不同步的c++多线程

C++ multithreading without synchronization

本文关键字:多线程 c++ 同步      更新时间:2023-10-16

c++:我有2个线程,每一个增量'n'通过做n++ (n是全局变量)假设我不使用任何线程同步,我的要求是打印0,1,2,....10.

int n = 0 //global variable
线程1:

n++;
printf("%d", n);
线程2:

n++;
printf("%d", n);

在没有线程同步的情况下执行程序会有问题吗,或者它会满足我的要求(1,2,3…10)

c++标准说这个程序有未定义的行为,任何事情都可能发生。任何,包括重复打印相同的数字,不打印任何内容,使程序崩溃或擦除硬盘。

1.10 (intro.multithread)

如果其中一个修改了内存位置(1.7),而另一个读取或修改了相同的内存位置,则两个表达式求值会发生冲突。

…如果程序的执行包含两个潜在的并发冲突操作,其中至少一个不是原子操作,并且两个操作都发生在另一个操作之前,则程序的执行包含数据竞争,下面描述的信号处理程序的特殊情况除外。任何这样的数据竞争都会导致未定义的行为。

对于被多个线程并发修改的变量,应该使用std::atomic<int>,或者与互斥锁同步,以避免由于数据竞争而导致的未定义行为。

对于您的用例,您需要确保每次变量更新时打印它的值,即增量和打印必须自动发生。简单地自动更新整数是不够的,您必须确保打印增量的结果,而不是在稍后的某个时间(当其他线程可能已经更改了它,如Rotem的回答所示)打印变量的值。

可以这样做:

std::atomic<int> n{0}; // global variable
线程1:

printf("%d", ++n);
线程2:

printf("%d", ++n);

现在,每次线程增加变量的增量结果将直接传递给printf

这假设对stdout的写入是线程安全的,并且是按顺序发生的,为了避免这种假设,使用互斥锁来创建一个临界区,更新变量并打印它,防止其他线程在互斥锁被锁定时增加变量或打印:

int n = 0 //global variable
std::mutex mtx; // global mutex
线程1:

{
  std::lock_guard<std::mutex> lock(mtx);
  ++n;
  printf("%d", n);
}
线程2:

{
  std::lock_guard<std::mutex> lock(mtx);
  ++n;
  printf("%d", n);
}

注意:仍然不能保证两个线程交替递增和打印,完全有可能第一个线程执行10次递增并打印整个输出,而第二个线程没有机会运行。

执行程序会有问题吗?

这是可能发生的(而且,正如墨菲所说:)。

理性

递增是不是原子的,通常使用以下三个基本操作来实现:

  1. 从内存中读取值
  2. 值加1
  3. 将值写回内存

注:printf()也不是原子。

线程之间的上下文切换可能随时发生,在n++printf()或这两个操作之间。竞争条件可能发生在上下文切换期间,如果n的访问未同步

例子

考虑以下场景:

    线程1读取值(值为10)线程2读取值(值为10)
  1. 线程1给值
  2. 加1
  3. 线程1将值写入内存(值为11)
  4. 线程2给值
  5. 加1
  6. 线程2将值写入内存(值仍然是11)

您期望的值是12,但实际是11。

结论

n为共享资源。

对共享资源的并发(读写)访问必须同步!