← SEARCHABLE ARCHIVE
Operating System · 2026-07-15

线程同步和竞争条件

为什么每次运行共享数据结果都会改变?

INTERACTIVE READING MODE →

该博客上撰写的文章是出于教学目的而创建的。 另外,文章中的信息可能不正确,请谨慎。

同一进程中的线程共享堆和全局数据。 因此,数据可以快速交换,但如果同时读取和写入相同的值,则每次执行的结果可能会有所不同。

这个问题称为竞争条件。

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

您需要养成始终以相同顺序获取锁的习惯,并且在持有锁时不要执行长时间运行的任务。

多线程的重点不是创建很多线程。 减少共享数据,明确定义必须共享的范围,并使用适合该范围的同步工具。