C++多线程:仅锁mutex传索引能否保证锁外写入跨线程可见
结论
当前的mutex加锁逻辑完全可以保证thread1在锁作用域外写入的foo[index]数据被thread2正确读取,你担心的编译器重排、处理器缓存一致性问题不会出现。
原因说明
1. 锁外写入期间不存在多线程竞争
当thread1从fooFree队列取出index后,这个index对应的Foo对象就处于thread1独占状态:它已经从空闲队列移除,还没被加入待处理队列,thread2根本拿不到这个index,不可能访问对应的foo[index]内存。直到thread1完成全部写入、下一轮循环加锁把index放入fooProcess后,thread2才有可能获取到这个index,写入全程没有并发访问。
2. mutex的内存语义提供了完整的可见性保证
C++标准强制要求std::mutex的unlock是release语义操作,lock是acquire语义操作,二者配对时会建立跨线程的同步关系:
- release语义保证:所有程序顺序上在
unlock之前的内存写入(无论写操作是在锁内还是锁外),都不允许被编译器、CPU重排到unlock之后,所有写入会在unlock执行前完成,对其他核心可见。你代码中锁外写入foo[index]的操作,从执行顺序上必然发生在thread1把index放入fooProcess后的unlock操作之前,会被release屏障完整覆盖。 - acquire语义保证:
lock操作之后的所有内存读操作,都不允许被重排到lock之前,且会同步之前同一个mutex上unlock操作发布的所有写入结果。thread2只有在lock成功、从fooProcess取出index之后才会读取foo[index],此时必然能看到thread1在unlock前完成的全部写入。
3. 完整的happens-before关系链
跨线程可见性的判断依赖完整的时序链,你的代码时序完全满足要求:
thread1写入foo[index] → (单线程顺序保证)thread1加锁把index推入fooProcess → thread1解锁mutex → (锁的同步保证)thread2加锁mutex取出index → (单线程顺序保证)thread2读取foo[index]
整个链条没有断点,读操作必然能观测到写操作的结果。
其他无关可见性的代码问题
- 两个工作线程都是
while(true)死循环,没有设置退出条件,程序永远无法正常终止。 main函数仅join了thread1,未回收thread2,程序退出时会触发未定义行为。- 全局的
foo向量在初始化resize(10)后没有再修改结构,元素地址固定,访问不存在vector本身的线程安全问题;如果后续会动态修改foo的大小,必须把vector结构修改操作放在锁保护范围内。
附测试代码:
#include <thread> #include <iostream> #include <vector> #include <mutex> #include <limits> constexpr size_t dataSize=1024*1024; struct Foo { int data[dataSize]; }; std::vector<Foo> foo; std::vector<size_t> fooFree; std::vector<size_t> fooProcess; std::mutex dataMutex; void thread1Func(); void thread2Func(); int main() { foo.resize(10); for(size_t i=0;i<10; ++i) fooFree.push_back(i); std::thread thread1=std::thread(&thread1Func); std::thread thread2=std::thread(&thread2Func); thread1.join(); thread2.join(); return 0; } void thread1Func() { size_t count=0; size_t index=std::numeric_limits<size_t>::max(); while(count < 10000) // 加退出条件避免死循环 { { std::unique_lock<std::mutex> lock(dataMutex); if(index != std::numeric_limits<size_t>::max()) { fooProcess.push_back(index); index=std::numeric_limits<size_t>::max(); } if(!fooFree.empty()) { index=fooFree.back(); fooFree.pop_back(); } } if(index != std::numeric_limits<size_t>::max()) { for(size_t i=0; i<dataSize; ++i) foo[index].data[i]=count+i; count++; } } } void thread2Func() { size_t recvCount=0; size_t index=std::numeric_limits<size_t>::max(); while(recvCount < 10000) // 加退出条件避免死循环 { { std::unique_lock<std::mutex> lock(dataMutex); if(index != std::numeric_limits<size_t>::max()) { fooFree.push_back(index); index=std::numeric_limits<size_t>::max(); } if(!fooProcess.empty()) { index=fooProcess.back(); fooProcess.pop_back(); } } if(index != std::numeric_limits<size_t>::max()) { // 校验数据正确性,如果可见性有问题会触发断言 for(size_t i=0; i<dataSize; ++i) { if(foo[index].data[i] != recvCount + i) { std::cerr << "data mismatch at index " << index << " pos " << i << " val " << foo[index].data[i] << " expected " << recvCount +i << std::endl; std::abort(); } } std::cout<<foo[index].data[0]<<"\n"; recvCount++; } } }
内容的提问来源于stack exchange,提问作者Krazer
相关产品推荐
相关产品推荐

