考虑以下简化程序建模真实场景,其中不同用户可以对同一资源发出并发请求:
#include <thread>
#include <memory>
#include <mutex>
#include <iostream>
using namespace std;
struct T {
void op() { /* some stuff */ }
~T() noexcept { /* some stuff */ }
};
std::shared_ptr<T> t;
std::mutex mtx;
std::weak_ptr<T> w{t};
enum action { destroy, op};
void request(action a) {
if (a == action::destroy) {
lock_guard<mutex> lk{mtx};
t.reset();
std::cout << "*t certainly destroyed\n";
} else if (a == action::op) {
lock_guard<mutex> lk{mtx};
if (auto l = w.lock()) {
l->op();
}
}
}
int main() {
// At some point in time and different points in the program,
// two different users make two different concurrent requests
std::thread th1{request, destroy}; std::thread th2{request, op};
// ....
th2.join();
th1.join();
}
我不是在问这个程序是否正式正确 - 我认为是这样,但我从未见过这种方法来保证通过智能指针共享资源的同步销毁。我个人认为这很好,并且有效使用。
但是,我想知道其他人是否也这么认为,如果除了与unique_lock
和条件变量的经典同步以及将修改(例如原子标志)引入{之外还有更优雅的替代方案{1}}。
如果我能以某种方式摆脱T
,那将是理想的。
答案 0 :(得分:1)
是的,没关系。 shared_ptr中的引用计数是原子的,并且锁定的副本在op的持续时间内保持在范围内,因此在op期间不能销毁该对象。
在这种情况下,互斥锁实际上并没有保护T的生命周期,而是对op()
的调用和破坏进行排序。如果您不介意多次并发调用op()
,或者销毁时间不确定(即上次运行op()
之后),那么您可以取消它,因为std::shared_ptr<>::reset()
并且std::weak_ptr<>::lock()
都是线程安全的。
但是,我建议谨慎,因为作者明确要求将op()
的来电序列化。