这段多线程代码应该怎么理解?

#include <stdio.h>
#include <pthread.h>

long count = 0;

void *run(void *n) {
  long i, nums = *((long *)n);
  for (i = 0; i < nums; i++) {
    count++;
  }
  return NULL;
}

void main() {
  pthread_t t1, t2;
  long nums = 100000;
  pthread_create(&t1, NULL, run, &nums);
  pthread_create(&t2, NULL, run, &nums);
  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  printf("count = %d", count);
}

如果我把 nums 设置为 10000,那么每次都准确输出 20000。或者设置的值比 10000 要少时也能准确输出。如果我把 nums 设置为 100000,那么每次输出的值都好像是随机的,范围在 100000 到 200000 之间。

如果是因为线程之间执行顺序的问题导致输出不确定,那为什么当值为 10000 时输出的值是比较确定的?

阅读 1.4k
avatarAI BotBETA

这段代码是一个简单的多线程程序,用于展示线程并发和增加全局变量的复杂行为。首先,我们先对代码进行简单解释。

#include <stdio.h>#include <pthread.h> 是标准库的头文件,分别用于标准输入输出和 POSIX 线程操作。

long count = 0; 定义了一个全局的 long 类型变量 count 并初始化为 0。

void *run(void *n) 是一个线程函数,它接受一个 void 指针 n,然后将其转换为 long 指针,解引用得到 nums 的值,然后在循环中将 count 增加 nums 次。

void main() 是程序的主函数,其中:

  • pthread_t t1, t2; 定义了两个线程的句柄。
  • long nums = 100000; 定义并初始化了一个 long 类型的变量 nums,值为 100000。
  • pthread_create(&t1, NULL, run, &nums);pthread_create(&t2, NULL, run, &nums); 创建了两个线程,并启动它们运行 run 函数。
  • pthread_join(t1, NULL);pthread_join(t2, NULL); 是阻塞调用,等待线程 t1 和 t2 完成。
  • 最后,程序输出 count 的值。

现在,让我们来看看程序的输出问题。当 nums 为 10000 时,每次都能准确输出 20000,这是因为两个线程每次都会将 count 增加 10000 次,所以总和是 20000。当 nums 的值比 10000 小时,结果也是准确的,因为两个线程增加的次数是固定的。

然而,当 nums 为 100000 时,输出是随机的。这是因为线程的调度是不确定的,可能存在一个线程先于另一个线程开始或结束的情况。这导致两个线程可能不会同时完成,因此输出的 count 值会在 100000 到 200000 之间波动。

此外,由于 count 是全局变量,没有进行同步操作(如互斥锁),所以当两个线程同时修改它时,可能会出现数据竞争(data race)。这可能是导致输出不稳定的原因之一。为了解决这个问题,可以使用互斥锁(如 pthread_mutex_t)来保护对 count 的访问。

5 个回答

这种情况下,两个线程有对同一变量的未经同步的写操作,所以存在 data race 。

存在 data race 的情况下,语言并不保证程序的结果。

也许你换台机器、换个编译器、换个编译选项、等等,就又能看到不同的现象。


对于貌似“确定”的结果,可能是由于在一个线程启动后,另一个线程还没有开始运行的很短的时间的,第一个线程的 10000 个循环已经做完了。所以两个线程对同一变量的操作实际并没有“重叠”。

当然也还可能有其它原因。

个人理解和CPU调度有关

两个线程全局共享变量count;

  1. 当nums <= 10000 时候,调度器可能把两次任务串行的放到同一个执行单元执行(线程Or?),不会产生数据竞争的情况,现在cpu的频率都是Ghz运行速度是很快的,是有这种可能的
  2. 当nums > 10000 时候,调度器可能把两次任务串行的放到两个不同执行单元执行(线程Or?),基本就会产生数据竞争的情况,比如某一次调度过程中此时缓存中的count = 1000,调度器切换到p1执行,cpu读取count的值然后进行加法运算后count的是1001,接下来p1准备把这个值(1001)写会到缓存中时,调度器切换到p2执行了, 然后cpu再读取缓存中的值1000,然后进行加法运算准备把这次运算后的结果(1001)写回到缓存时,调度器又切换到了p1,然后p1把它的运算结果1001写会缓存,然后调度器这个时候又切换到了p2,然后p2又把它的运算结果1001也写会到了缓存此时尽管两个线程进行了2次加法运行但是结果却增加了一次 这就是值小于等于 20000的原因

有一本书写的特别清楚我给你找找:

操作系统导论
这本数的第2章介绍并发的时候有详细介绍

另外一本是:
Is Parallel Programming Hard, And, If So, What Can You Do About It? (Release v2023.06.11a)

设置为10000或者更少的时候,执行时间短,另一个线程还没来得及和当前执行的线程竞争资源,当前执行的线程就执行完了。

这里存在线程安全问题,即多个线程同时修改 count 变量,可能导致结果不一致。

   long count = 0;
这个变量被所有线程共享,没有采取任何同步措施。
   void *run(void *n) {
     long i, nums = *((long *)n);
     for (i = 0; i < nums; i++) {
       count++; // 对全局变量 count 进行无保护的并发递增
     }
     return NULL;
   }
   

对于输出结果的不一致性:
当 nums 较小时(如 10000),由于循环次数较少,线程间发生数据竞争的概率相对较小,因此输出结果可能看起来比较稳定,但这种情况并不绝对,仍有可能因为线程调度等因素出现偶尔的数据竞争和结果不一致。
当 nums 较大时(如 100000),线程间的并发更新次数大大增加,数据竞争变得更为频繁,因此会出现明显的不一致现象,导致输出的结果在某个范围内波动。
要解决这个问题,应该引入线程同步机制,例如使用互斥锁(mutex)确保同一时间只有一个线程能访问和修改 count 变量,以保证结果正确无误。例如

#include <pthread.h>

long count = 0;
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;

void *run(void *n) {
  long i, nums = *((long *)n);
  for (i = 0; i < nums; i++) {
    pthread_mutex_lock(&count_mutex);
    count++;
    pthread_mutex_unlock(&count_mutex);
  }
  return NULL;
}
新手上路,请多包涵

个人以为:错误的原因是多线程竞争,数太小没有影响是因为太快了,线程2还在创建,线程1已经执行完毕

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题