为什么将条件写转换为无条件写不是线程安全的优化?
Why is transforming a conditional write to an unconditional write not a thread safe optimization?
在一篇关于并发性和c++ 11内存模型的演讲中,Herb Sutter给出了一些非法优化的例子。
http://channel9.msdn.com/Shows/Going +深/cpp -和-在- 2012 -草-萨特-原子武器- 2 - 2
从第17分钟的幻灯片:
void f(vector<widget>& v) {
if(v.length()>0) xMutex.lock();
for(int i = 0; i < v.length(); ++i)
++x; // write is conditional
if(v.length()>0) xMutex.unlock();
}
"很可能(如果存在严重缺陷)对中心循环进行转换:"
r1 = x;
for(int i = 0; i < v.length(); ++i)
++r1; // oops: write is not conditional
x = r1;
他解释说:"……这个写不是有条件的,它会发生在每次执行,即使doOptionalWork是假的,这将注入一个写不受互斥锁保护,注入一个竞争…"
为什么他说发明的写不受互斥锁的保护?我理解完整的转换描述如下:
// "optimized" version 1
void f(vector<widget>& v) {
if(v.length() > 0) xMutex.lock()
r1 = x;
for(int i = 0; i < v.length(); ++i)
++r1;
x = r1;
if(v.length() > 0) xMutex.unlock();
}
但也可以是这个
// "optimized" version 2
void f(vector<widget>& v) {
if(v.length() > 0) xMutex.lock()
r1 = x;
for(int i = 0; i < v.length(); ++i)
++r1;
if(v.length() > 0) xMutex.unlock();
x = r1;
}
显然版本2不是线程安全的,但我不确定版本1。版本1线程安全吗?如果没有其他写x的行呢?
刚才我开始输入"要么v.length()是0,要么不是…",并意识到即使重言式在多线程世界中也会失败。我不知道该从何说起
互斥锁仅在vector容器内有对象时使用。在两个空向量上并发地运行这个方法会导致数据竞争,因为我们根本没有锁,但是我们写到了x。
假设有另一个线程执行以下代码:
xMutex.lock()
++x;
xMutex.unlock();
如果上面的代码与(转换后的)函数f
(带一个空向量)同时执行,那么x
的增量可能会丢失,尽管这在源代码级别上是不可能的。
mutex. lock()
函数调用锁定互斥锁。互斥锁是一个互斥锁,它只允许一个调用者调用lock()
并返回。随后的调用者将阻塞,等待原始调用者调用unlock()
。
它是'互斥'的,因为只有一个线程被允许获得锁。
这就是互斥锁的定义。
在你发布的代码中,第二个块没有用lock()
和unlock()
调用包装增量循环。
这意味着调用在循环中修改变量的函数的两个线程将彼此踩在一起。可以在另一个循环读取该变量之前(或之后)立即写入该变量。代码期望变量与函数一致,因此函数的行为将是不正确的。
考虑到这一点,我制作了一个演示来演示比赛。
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
// we have very simple widgets
typedef int widget;
// x and its mutex are shared by the threads
static int x = 0;
std::mutex xMutex;
static std::chrono::milliseconds central_loop_dur(10);
static std::chrono::milliseconds before_central_loop(0);
void f(std::vector<widget>& v) {
// The first thread acquires the lock.
// The second thread has an empty vector so passes through before the lock is released.
if(v.size() > 0) xMutex.lock();
// The first thread will take about 50ms to write to x.
// The second thread reads x nearly concurrently with the first thread. Both see 0.
int r = x;
// The first thread passes through here.
// The second thread snoozes.
std::this_thread::sleep_for(before_central_loop);
before_central_loop = std::chrono::milliseconds(200);
for(auto i : v) {
std::this_thread::sleep_for(central_loop_dur);
++r;
}
// At 50ms, the first thread writes to x.
// At 200ms, the second thread obliviously writes a previous value of x back to x.
x = r;
if(v.size() > 0) xMutex.unlock();
}
void thread_main(size_t vec_size) {
std::vector<widget> v(vec_size);
f(v);
}
int main() {
std::thread t1(thread_main, 5);
std::thread t2(thread_main, 0);
t1.join();
t2.join();
std::cout << "The value of x = " << x << std::endl;
}
如果没有睡眠指令,如果一切都按顺序发生,结果将是x = 5。相反,程序将(很有可能)打印x = 0,即使线程1增加x 5次!
- 从不同线程使用int64的不同字节安全吗
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 在std::thread中,joinable()然后join()线程安全吗
- 在c++队列中使用pop和visit实现线程安全
- 以线程安全的方式调用"QQuickPaintedItem::updateImage(const QImage&image)"(no QThread)
- 全局变量 多读取器 一个写入器多线程安全?
- 共享队列的线程安全
- boost::文件系统::recursive_directory_iterator多线程安全
- 静态 constexpr 类成员变量对多线程读取是否安全?
- 以线程安全的方式转换 C/C++ 中时区名称字符串的时区偏移量
- 线程安全运算符<<
- 如何使缓存线程安全
- C++线程安全:如果只有一个线程可以写入非原子变量,但多个线程从中读取. 会遇到问题吗?
- 提升精神 V2 Qi 语法线程安全吗?
- 线程调用的函数对对象删除是否安全?
- asio 链对象线程安全吗?
- 线程安全队列 c++
- 提供对不同类型的数据(建议、代码审查)的线程安全访问的类
- 有没有更好的方法可以使此代码线程安全?线程局部静态似乎是一个生硬的工具