线程间数据共享存在的问题
多个线程同时读取一个变量没问题,多个线程同时写一个变量问题很大
利用互斥量去解决
mutex m;
m.lock();
Write(varible);
m.unlock();
由于每次都要unlock,类似于new和delete, 所以都采用RAII手法,这里用的是lock_guard(),使用方法如下:
{
std::mutex m;
lock_guard<std::mutex> guard(m);//离开作用域会自动解锁
Write(varible);
}
互斥量存在的条件竞争问题
比如一个stack对象obj,当线程1在调用obj.size()的时候,线程2在执行obj.pop()函数,虽然每个函数都加锁,比如线程1在方向size=1,接下来做pop操作时候,线程2已经把数据pop出去了,这样一来就会报错,所以在设计多线程时候还是要想清楚。
互斥量存在的死锁问题
//线程1
m1.lock();
m2.lock();
Play(keyboard, mouse);
m1.unlock();
m2.unlock();
//线程2
m2.lock();
m1.lock();
Play(mouse,keyboard);
m2.unlock();
m1.unlock();
可以看到,当我们在用电脑时候,要鼠标和键盘一起使用,这里有个问题,就是线程1是先抢键盘,而线程2是先抢鼠标,这样两个线程就会出现死锁现象。解决方法是线程1和2都是先抢键盘再抢鼠标,或者同时锁住(C++提供了std::lock()函数)。
//方法一
//线程1,2
m1.lock();
m2.lock();
Play(keyboard, mouse);
m1.unlock();
m2.unlock();
//方法二
std::lock(m1, m2);
lock_guard<mutex> lock_a(m1, std::adopt_lock);//adopt_lock表示互斥已经锁了,不用在这里锁了
lock_guard<mutex> lock_a(m2, std::adopt_lock);
利用其他工具去解决
call_once,once_flag
多线程遇到的初始化问题,这个具体参考单例模式中遇到的问题,书本中提供了一段代码解释为什么double-check有问题,但是我觉得书上解释的有问题,本质double-check的逻辑有问题还是因为reorder的问题,和单例模式中的问题一样
void do()
{
if(nullptr == m_log) //避免初始化后每次依然加锁,影响性能
{
Lock lock;
if(nullptr == m_log)// 避免第一次初始化时候出现多个线程都进入了代码块,生成多个实列
{ m_log = new Log();}
}
m_log->do_something();
}
可以知道其实当线程1已经new一个log对象的时候,即使还没完成构造就返回了指针,当线程2在判断m_log是不是为空时,发现不为空就直接do_something(), 但是此时线程1还没完成构造,这里为什么会出现这种情况是因为部分编译器优化导致的。如何解决这个问题?c++提供了call_once,once_flag的方式,简单的意思就是把初始化代码包裹成一个原子操作
,具体实现是利用栅栏技术,反正就是保证初始化的完整,如果多个线程同时走到call_once的时候,会有一个线程被激活去处理init_func, 一旦处理完后,以后其他线程再遇到就不用看这一行代码了。
std::once_flag flag;
void do()
{
std::call_once(flag, init_func);
m_log->do_something();
}