主页 > 知识库 > 服务器 > Linux/BSD >

Linux多线程与同步

来源:网络 作者:佚名 发表于:2013-01-23 10:00  点击:
典型的UNIX系统都支持一个进程创建多个线程(thread)。在Linux进程基础中提到,Linux以进程为单位组织操作,Linux中的线程也都基于进程。尽管实现方式有异于其它的UNIX系统,但Linux的多线程在逻辑和使用上与真正的多线程并没有差别。 1. 多进程 我 们先来看一
典型的UNIX系统都支持一个进程创建多个线程(thread)。在Linux进程基础中提到,Linux以进程为单位组织操作,Linux中的线程也都基于进程。尽管实现方式有异于其它的UNIX系统,但Linux的多线程在逻辑和使用上与真正的多线程并没有差别。
1. 多进程
我 们先来看一下什么是多线程。在Linux从程序到进程中,我们看到了一个程序在内存中的表示。这个程序的整个运行过程中,只有一个控制权的存在。当函数被 调用的时候,该函数获得控制权,成为激活(active)函数,然后运行该函数中的指令。与此同时,其它的函数处于离场状态,并不运行。如下图所示:

Linux从程序到进程
我们看到,各个方块之间由箭头连接。各个函数就像是连在一根线上一样,计算机像一条流水线一样执行各个函数中定义的操作。这样的一个程序叫做单线程程序。
多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行。即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果。如下图所示,就是一个多线程的流程:

main()到func3()再到main()构成一个线程,此外func1()和func2()构成另外两个线程。操作系统一般都有一些系统调用来让你将一个函数运行成为一个新的线程。
回 忆我们在Linux从程序到进程中提到的stack的功能和用途。由于stack中只有最下方的stack frame可以被读取,所以我们只能有该frame对应的单一函数处于激活状态。为了实现多线程,我们必须绕开stack带给我们的限制。为此,当我们创 建一个新的线程的时候,我们为这个线程创建一个新的stack。当该stack执行到全部弹出的时候,该线程完成任务并结束。所以,多线程的进程在内存中 会有多个stack,相互之间以一定的空白区域隔开,以备stack的增长。每个线程随时可以使用自己stack中最下方的stack frame中的参数和变量,并与其它线程共享内存中的Text,heap和global data区域。在上面的例子中,我们将在进程空间中有三个stack。
(要注意的是,对于多线程来说,由于同一个进程空间中存在多个stack,任何一个空白区域被填满都会导致stack overflow的问题。)
2. 并发
多 线程相当于一个并发(concunrrency)系统。并发系统一般同时执行多个任务。如果多个任务可以共享资源,特别是同时写入某个变量的时候,就需要 解决同步的问题。比如说,我们有一个多线程火车售票系统,用全局变量i存储剩余的票数。多个线程不断地卖票(i = i - 1),直到剩余票数为0。所以每个都需要执行如下操作:
双击代码全选
1
2
3
4
5
6
7
8
9
/*mu is a global mutex*/
        
while (1) {                        /*infinite loop*/
    if (i != 0) i = i -1
    else {  
      printf("no more tickets");  
      exit();  
    }  
}
 
如果只有一个线程执行上面的程序的时候(相当于一个窗口售票),则没有问题。但如果多个线程都执行上面的程序(相当于多个窗口售票), 我们就会出现问题。我们会看到,其根本原因在于同时发生的各个线程都可以对i读取和写入。
我 们这里的if结构会给CPU两个指令, 一个是判断是否有剩余的票(i != 0), 一个是卖票 (i = i -1)。某个线程会先判断是否有票(比如说此时i为1),但两个指令之间存在一个时间窗口,其它线程可能在此时间窗口内执行卖票操作(i = i -1),导致该线程卖票的条件不再成立。但该线程由于已经执行过了判断指令,所以无从知道i发生了变化,所以继续执行卖票指令,以至于卖出不存在的票 (i成为负数)。对于一个真实的售票系统来说,这将成为一个严重的错误 (售出了过多的票,火车爆满)。
在并发情况下,指令执行的先后顺序 由内核决定。同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清除哪一个会先执行。如果运行的结果依赖于不同线程执行的先后的话,那么 就会造成赛跑状况(race condition),在这样的状况下,计算机的结果很难预知。我们应该尽量避免赛跑状况的形成。最常见的解决赛跑状况的方法是将原先分离的两个指令构成 不可分隔的一个原子操作(atomic operation),而其它任务不能插入到原子操作中。
3. 多进程同步(synchronization)
对 于多线程程序来说,同步是指在一定的时间内只允许某一个线程访问某个资源 。而在此时间内,不允许其它的线程访问该资源。我们可以通过互斥锁(mutex),条件变量(condition variable)和读写锁(reader-writer lock)来同步资源。
1) mutex
mutex是一个特殊 的变量,它有锁上(lock)和打开(unlock)两个状态。mutex一般被设置成全局变量。打开的mutex可以由某个线程获得。一旦获得,这个 mutex会锁上,此后只有该线程有权打开。其它想要获得mutex的线程,会等待直到mutex再次打开的时候。我们可以将mutex想像成为一个只能 容纳一个人的洗手间,当某个人进入洗手间的时候,可以从里面将洗手间锁上。其它人只能在mutex外面等待那个人出来,才能进去。在外面等候的人并没有排 队,谁先看到洗手间空了,就可以首先冲进去。
上面的问题很容易使用mutex的问题解决,每个进程的程序可以改为:
双击代码全选
1
2
3
4
5
6
7
8
9
10
11
/*mu is a global mutex*/  
        
while (1) {                /*infinite loop*/  
  mutex_lock(mu);           /*aquire mutex and lock it, if cannot, wait until mutex is unblocked*/  
  if (i != 0) i = i - 1;  
  else {  
    printf("no more tickets");  
    exit();  
  }  
  mutex_unlock(mu);         /*release mutex, make it unblocked*/
}
 
第一个执行mutex_lock()的线程 会先获得mu。其它想要获得mu的线程必须等待,直到第一个线程执行到mutex_unlock()释放mu,才可以获得mu,并继续执行线程。所以线程 在mutex_lock()和mutex_unlock()之间的操作时,不会被其它线程影响,就构成了一个原子操作。
需要注意的时候,如 果存在某个进程依然使用原先的程序 (即不尝试获得mu,而直接修改i),mutex不能阻止该程序修改i,mutex就失去了保护资源的意义。所以,mutex机制需要程序员自己来写出完 善的程序来实现mutex的功能。我们下面讲的其它机制也是如此。
2) condition variable
condition variable是另一种常用的变量。它也常常被保存为全局变量,并和mutex合作。
假 设我们有这样一个状况: 我们有100个工人,每人负责装修一个房间。当有10个房间装修完成的时候,我们就通知相应的十个工人一起去喝啤酒。我们如何实现呢?我们可以让工人在装 修好房间之后,去检查已经装修好的房间数。但多线程条件下,会有赛跑状况的危险(其他工人会在该工人装修好房子和检查之间完成工作)。
双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*mu: global mutex, cond: global codition variable, num: global int*/
mutex_lock(mu)  
        
num = num + 1;                      /*worker build the room*/
        
if (num <= 10) {                     /*worker is within the first 10 to finish*/
    cond_wait(mu, cond);            /*wait*/
    printf("drink beer");  
}  
else if (num = 11) {                /*workder is the 11th to finish*/
  cond_broadcast(mu, cond);         /*inform the other 9 to wake up*/
}  
        
mutex_unlock(mu);
 
通常, condition variable除了要和mutex配合之外,还需要和另一个全局变量配合(这里的num, 也就是装修好的房间数)。这个全局变量用来构成各个条件。

有帮助
(1)
100%
没帮助
(0)
0%