信息

多线程初试

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

int glob = 6;
void increse_num(int *arg);

int main() {
  int ret;
  pthread_t thrd1, thrd2, thrd3;
  int flag = 1;
  ret = pthread_create(&thrd1, NULL, (void *)increse_num, &flag);
  sleep(1);  //如果无sleep(1)结果会有什么不同?
  flag = 2;
  ret = pthread_create(&thrd2, NULL, (void *)increse_num, &flag);
  pthread_join(thrd1, NULL);
  pthread_join(thrd2, NULL);
  flag = 0;
  increse_num(&flag);  //如果放在pthread_join之前结果会怎样?
  return 0;
}

void increse_num(int *arg) {
  int flag = *arg;
  if (flag == 0) {
    glob = glob + 1;
    // sleep(10);  //如果都加了sleep,输出的结果有什么不同? 
		// 没有什么不同
    printf("parent %d: glob=%d\\n", flag, glob);
  } else if (flag == 1) {
    glob = glob + 2;
    // sleep(10);
    printf("child %d: glob=%d\\n", flag, glob);
  } else {
    glob = glob + 3;
    printf("child %d: glob=%d\\n", flag, glob);
  }
}

运行结果为:

➜  src git:(master) ✗ ./thread_demo_single_var
child 1: glob=8
child 2: glob=11
parent 0: glob=12
➜  src git:(master) ✗ 

此时,运行顺序为:

  1. 创建 thread1
  2. thread1 执行完毕, glob: 6 => 8
  3. 创建 thread2
  4. thread2 执行完毕, glob: 8 => 11
  5. thread1 和 thread2 执行完毕,主线程继续向下执行
  6. glob: 11 => 12

如果去掉创建 thrd1 之后的 Sleep() ,结果为:

➜  src git:(master) ✗ ./thread_demo_single_var_s1
child 2: glob=9
child 2: glob=12
parent 0: glob=13
➜  src git:(master) ✗

可以看到,两个线程都表示自己的 flag2 ,显然是不太“符合逻辑”的。那么,这是为什么呢?

在去掉了 Sleep(1) 之后,thread1 没有执行完就执行了 thread2,两个线程几乎同时开始执行;而这时候主线程已经让 flag = 2 了。两个线程并没有像我们想象中那样先运行 thread1 然后再执行 thread2,从而在一些变量上产生了不确定性:如果在主线程执行 flag = 2 之前两个线程都执行完了,那么两个线程都认为 flag = 1 ;如果在主线程执行 flag = 2 之后两个线程才执行完,那么两个线程都认为 flag = 2

这样对资源(全局变量)“竞争”的场景并不存在于多进程当中,因为多进程产生的时候会完全复制整个程序的堆栈,使得变量实际上并不共享,多进程之间的通信依赖系统内核方法等方式实现,而多线程中则需要应用程序自己管理需要共同操作的变量。

多线程对资源的竞争还会表现在对 IO 资源 / 内存资源等只能由一个线程正常操作的资源上,而且如果管理不当后果更加严重。比如,同时对一个文件写入,或者同时向屏幕输出内容,很有可能会得到互相穿插的两个数据片段而造成数据丢失或者产生异常。

解决的方法有加锁、抛出/捕获异常等,之后再说吧。