为什么 shared_ptr 对象在这种情况下没有锁也能正常工作?
Why shared_ptr object works well without lock in such case?
一个reader线程和多个写入线程同时访问shared_ptr对象并且它运行良好,代码如下(但是,如果我将写入行代码从“=”修改为“重置” ,它会在阅读时进行核心转储):
shared_ptr.reset 表示 coredump 而“operator =”表示运行良好? (我试了100多次)
bool start = false;
std::mutex mtx;
std::condition_variable cond;
std::shared_ptr<std::string> string_ptr(new std::string("hello"));
int main() {
auto read = []() {
{
std::cout << "readddd" << std::endl;
std::unique_lock<std::mutex> lck(mtx);
while (!start) {
cond.wait(lck);
}
}
for (int i = 0; i < 100000; ++i) {
std::cout << *string_ptr.get() << std::endl;
}
};
auto write = []() {
{
std::unique_lock<std::mutex> lck(mtx);
while (!start) {
cond.wait(lck);
}
}
for (int i = 0; i < 100000; ++i) {
string_ptr = std::make_shared<std::string>(std::to_string(i));
// string_ptr.reset(new std::string(std::to_string(i))); // will coredump
}
};
std::thread w(write);
std::thread rthreads[20];
for (int i = 0; i < 20; ++i) {
rthreads[i] = std::thread(read);
}
{
std::unique_lock<std::mutex> lck(mtx);
start = true;
cond.notify_all();
}
w.join();
for (auto& t : rthreads) {
t.join();
}
return 0;
}
coredumpe 堆栈将是:
#0 0x00007fee5fca3113 in std::basic_ostream& std::operator<< (std::basic_ostream&, std::basic_string const&) () 来自 /lib64/libstdc++.so.6
#1 0x00000000004039f0 in ::operator()(void) const (__closure=0xa54f98) 在 test_cast.cpp:395
行“std::cout << *string_ptr.get() << std::endl;”
两个版本都存在竞争条件。 std::shared_ptr
并不是神奇的线程安全。如果你想在线程之间共享它,你必须用互斥锁来保护它。只是你尝试了几次并不能证明:它可能只是不太可能导致错误,甚至你的 compiler/OS/CPU 组合不可能出现错误,但这并不能保证。
如果您想看到它更频繁地中断,请将切换到线程上下文的代码插入到代码中。最简单的方法是休眠一段时间,此时 OS 调度不同的线程:
for (int i = 0; i < 100000; ++i) {
std::string* tmp = string_ptr.get();
std::this_thread::yield();
std::cout << *tmp << std::endl;
}
请注意,此代码强制执行上下文切换,但当超过线程的时间片时,相同的切换可能会自发地发生在同一点。
至于为什么一个比另一个更容易失败,请逐步查看 shared_ptr
的赋值运算符和 reset()
成员函数执行的指令。它们的实现可能有更大或更小的临界区,这控制了错误出现的频率。
一个reader线程和多个写入线程同时访问shared_ptr对象并且它运行良好,代码如下(但是,如果我将写入行代码从“=”修改为“重置” ,它会在阅读时进行核心转储):
shared_ptr.reset 表示 coredump 而“operator =”表示运行良好? (我试了100多次)
bool start = false;
std::mutex mtx;
std::condition_variable cond;
std::shared_ptr<std::string> string_ptr(new std::string("hello"));
int main() {
auto read = []() {
{
std::cout << "readddd" << std::endl;
std::unique_lock<std::mutex> lck(mtx);
while (!start) {
cond.wait(lck);
}
}
for (int i = 0; i < 100000; ++i) {
std::cout << *string_ptr.get() << std::endl;
}
};
auto write = []() {
{
std::unique_lock<std::mutex> lck(mtx);
while (!start) {
cond.wait(lck);
}
}
for (int i = 0; i < 100000; ++i) {
string_ptr = std::make_shared<std::string>(std::to_string(i));
// string_ptr.reset(new std::string(std::to_string(i))); // will coredump
}
};
std::thread w(write);
std::thread rthreads[20];
for (int i = 0; i < 20; ++i) {
rthreads[i] = std::thread(read);
}
{
std::unique_lock<std::mutex> lck(mtx);
start = true;
cond.notify_all();
}
w.join();
for (auto& t : rthreads) {
t.join();
}
return 0;
}
coredumpe 堆栈将是:
#0 0x00007fee5fca3113 in std::basic_ostream
行“std::cout << *string_ptr.get() << std::endl;”
两个版本都存在竞争条件。 std::shared_ptr
并不是神奇的线程安全。如果你想在线程之间共享它,你必须用互斥锁来保护它。只是你尝试了几次并不能证明:它可能只是不太可能导致错误,甚至你的 compiler/OS/CPU 组合不可能出现错误,但这并不能保证。
如果您想看到它更频繁地中断,请将切换到线程上下文的代码插入到代码中。最简单的方法是休眠一段时间,此时 OS 调度不同的线程:
for (int i = 0; i < 100000; ++i) {
std::string* tmp = string_ptr.get();
std::this_thread::yield();
std::cout << *tmp << std::endl;
}
请注意,此代码强制执行上下文切换,但当超过线程的时间片时,相同的切换可能会自发地发生在同一点。
至于为什么一个比另一个更容易失败,请逐步查看 shared_ptr
的赋值运算符和 reset()
成员函数执行的指令。它们的实现可能有更大或更小的临界区,这控制了错误出现的频率。