一:用互斥量mutex保护受访问的数据
int i = 0;
mutex m;
void test()
{
m.lock();//加锁
i = i + 1;
printf("i=%d\n", i);
m.unlock();//释放锁
}
int main()
{
thread t1(test);
thread t2(test);
t1.join();
t2.join();
printf("main thread: i=%d\n", i);
return 0;
}
和thread要调用join类似, mutex也要确保调用了unlock, 否则另一个线程会无休止等待锁
类lock_guard可以确保该类析构时释放锁(和jthread类似)
这样test函数可写为
void test()
{
//c++17支持类模板参数推导, c++17之前需要lock_guard<mutex> guard(m)
//此外c++17还引入了scoped_lock, 是增强版的lock_gard, 也可scoped_lock guard(m)
lock_guard guard(m);
i = i + 1;
printf("i=%d\n", i);
}
二:多线程下的容器stack探讨
假如我们实现了一个用mutex保护的多线程stack
template<typename T>
class my_stack
{
stack<T> s;
mutex m;
public:
uint32_t size()
{
lock_guard guard(m);
return s.size();
}
bool empty()
{
lock_guard guard(m);
return s.empty();
}
void push(T const&);
T& top();
void pop();
};
这种实现会有问题:
empty()和size()的结果不可信, 因为即使内部加锁保护, 但函数一旦返回后其他线程就不受限制, 可以随意添加或修改元素, 返回结果并不代表实时结果
考虑如下行为
my_stack<int> s;
if (!s.empty())
{
int value = s.top();
s.pop();
}
1, s.empty的判断结果会失效: 一个线程判断非空进入if语句块, 另一个线程马上pop掉最后一个元素, 此时该stack变空了, 在空stack上调用top会导致未定义行为
2, 一种可能的执行顺序
在此执行顺序下, 线程AB的value为同一个值, 然后各pop掉一个元素, 这样会导致pop的第二个元素未被读取就丢掉了
----
针对问题更改方案, 考虑把empty,pop和top组成一个函数, 加以保护, 如下
T top()
{
lock_guard guard(m);
if (!s.empty())
{
T value = s.top();
s.pop();