线程同步和竞争条件
为什么每次运行共享数据结果都会改变?
该博客上撰写的文章是出于教学目的而创建的。 另外,文章中的信息可能不正确,请谨慎。
同一进程中的线程共享堆和全局数据。 因此,数据可以快速交换,但如果同时读取和写入相同的值,则每次执行的结果可能会有所不同。
这个问题称为竞争条件。
count++ 不是一次性操作
int count = 0;
void AddCount()
{
++count;
}
它可能是一行代码,但在 CPU 上可能是多个步骤:读取一个值,加 1,然后再次存储它。
线程 A 读取计数 0
线程 B 读取计数 0
线程A存储1
线程B存储1
我们将其递增两次,但最终结果是 1,而不是 2。 由于线程的执行顺序根据操作系统情况而变化,这可能是测试过程中偶尔出现的错误。
互斥锁一次只允许一个线程进入
std::mutex countMutex;
int count = 0;
void AddCount()
{
std::lock_guard<std::mutex> lock(countMutex);
++count;
}
当锁定互斥锁的线程执行临界区时,其他线程会等待。 lock_guard如果使用 ,即使函数结束或发生异常,锁也会自动释放。
原子可用于简单值
原子操作可用于小型操作,例如增加整数。
std::atomic<int> count = 0;
void AddCount()
{
++count;
}
然而,如果需要同时安全地维护多个变量之间的关系,仅用一个原子是无法解决的。 例如,如果金币扣除和物品添加必须同时成功,则两者都必须受到单个关键区域的保护。
您还应该小心死锁
如果两个线程等待另一个线程持有的锁,则没有人可以继续。 这称为死锁。
线程A:持有锁1并等待锁2
线程B:持有锁2并等待锁1
您需要养成始终以相同顺序获取锁的习惯,并且在持有锁时不要执行长时间运行的任务。
多线程的重点不是创建很多线程。 减少共享数据,明确定义必须共享的范围,并使用适合该范围的同步工具。