C语言的线程不同步问题的研究

线程的概念

线程就是运行在进程上下文的逻辑流。程序是由进程中的线程构成的,现代操作系统允许我们在一个进程中使用多个线程,线程由内核自动调度。每个线程有自己的线程上下文,包括一个唯一的整数线程ID(thread ID,tid),栈,栈指针,程序计数器,通用目的寄存器和条件码。所有运行在同一个进程上的线程共享该进程的整个虚拟地址空间。每一个进程开始生命周期时都是单一线程,这个线程称为主线程。在某一时刻,主线程创建一个对等线程,从这个时间点开始,两个线程就会并发运行。

线程执行不同于进程执行,因为一个线程的上下文要比一个进程的上下文小的多,线程的上下文切换较进程的上下文切换容易得多,和一个进程相关的线程组成一个线程池,独立于其他进程创建的线程。主线程和其他线程的区别仅仅在于它总是进程中第一个运行的线程。对等线程池的概念的主要影响是,一个线程可以杀死它的任何对等线程,或者等待它的任何对等线程终止。另外,每个对等线程都能读写相同的共享数据。

Posix线程(Pthreads)是在C程序中处理线程的一个标准接口,它最早出现在1995年,而且在大多数unix系统都可用。它可以允许程序创建/杀死/回收线程,与对等线程安全地共享数据,还可以通知对等线程系统状态的变化。

线程不同步案例

线程很有吸引力的一方面是因为多个线程可以很容易共享相同的程序变量。然而,这种共享也很棘手,为了编写正确的现成化程序,我们必须对所谓的共享以及如何工作有所了解。

线程存储器模型,根据存储类型分有三种:1.全局变量,全局变量定义在函数外面,在运行时,虚拟存储器的读/写区域只包含每个全局变量的一个实例,任何线程可以引用。2.本地自动变量,本地自动变量就是定义在函数内部但是没有static属性的变量,在运行时,每个线程的栈都包含它自己的所有本地自动变量的实例。即使多个线程执行同一个线程例程时也是如此。3.本地静态变量,函数内部并有static属性的变量。和全局变量一样,虚拟存储器的读/写区域只包含在程序中声明的每个本地静态变量的一个实例。

下面一段代码演示了共享变量的同步错误的可能性。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void *thread(void *vargp);
volatile int cnt=0;
int main(int argc,char* argv[])
{
    int niters;
    pthread_t pt1,pt2;
    if(argc!=2) {
        fprintf(stderr,"usage:%s <niters>\n",argv[0]);
        return 1;
    }
    niters=atoi(argv[1]);
    pthread_create(&pt1,NULL,thread,&niters);
    pthread_create(&pt2,NULL,thread,&niters);
    pthread_join(pt1,NULL);
    pthread_join(pt2,NULL);
    if(cnt!=(2*niters)) {
        fprintf(stdout,"Boom,cnt=%d\n",cnt);
        return 0;
    }
    else {
        fprintf(stdout,"OK cnt=%d\n",cnt);
        return 0;
    }
}
void *thread(void *vargp)
{
    int i,niters=*((int *)vargp);
    for(i=0;i<niters;i++)
        cnt++;
    return NULL;
}

我们写出这个文件的简单Makefile文件:

# this is a makefile!
main.out: main.c
    gcc main.c -o main.out -pthread

注意,C语言的线程程序编译时需要加上-pthread选项。

我们执行结果如下:

zzw@zzw-ThinkPad-Edge-E430c:~/c/thread/demo1$ ./main.out 10000
Boom,cnt=11493
zzw@zzw-ThinkPad-Edge-E430c:~/c/thread/demo1$ ./main.out 10000
Boom,cnt=12066
zzw@zzw-ThinkPad-Edge-E430c:~/c/thread/demo1$ ./main.out 10000
Boom,cnt=11759
zzw@zzw-ThinkPad-Edge-E430c:~/c/thread/demo1$ ./main.out 10000
Boom,cnt=10908

可以看到,四次执行都发生了同步错误,即共享变量没有被正确共享。

其实,线程函数代码thread部分我们可以分为以下五个部分:

  1. 在循环头部的指令块

  2. 加载共享变量cnt到寄存器%eax的指令,这里%eax表示线程i中的寄存器%eax的值

  3. 更新%eax的指令

  4. 将%eax的更新值存回到共享变量cnt的指令

  5. 循环尾部的指令块

总结

因此,我们很容易看到,从第2步到第4步是不安全区,即这三步是不能被其他线程打断的,需要锁定,即是个原子操作,只有这样才能保证程序的全局变量能够正确共享。而上面的代码中并没有体现这一点,因此无法正确运行结果。

你可能感兴趣的:(同步,共享变量,C线程)