原子操作对同步与互斥的意义
一. 原子操作的定义
原子操作,是指一组相关联的操作要么都不间断的执行,要么都不执行。
二. 原子操作对同步与互斥的意义
1. 讨论原子操作的意义之前,先了解操作系统中如下概念:
竞争条件:两个或多个进程或线程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞争条件。
临界区:把对共享内存进行访问的程序片段称作临界区。
2. 竞争问题举例
考虑两个线程打印数字10~1,线程总是在打印counter当前值后对其执行减一操作,代码如下。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 5 int counter=10; 6 7 void *mythread(); 8 9 int main(void) 10 { 11 void *ret1,*ret2; 12 pthread_t t1,t2; 13 pthread_create(&t1,null,&mythread,null); 14 pthread_create(&t2,null,&mythread,null); 15 pthread_join(t1,&ret1); 16 pthread_join(t2,&ret2); 17 return 0; 18 } 19 20 void *mythread() 21 { 22 while(counter>0){ 23 printf("%d\n",counter); 24 counter--; 25 } 26 27 pthread_exit("finished\n"); 28 }
若是单线程的情况下,这种打印方式不会有任何问题。然而两个线程的执行结果如下图所示,多输出一个数字10。这种情况是由于一个线程判断并输出变量counter之后,执行counter减一操作之前,另一个线程也开始判断并输出变量counter,这个时候counter的值还未来的及发生改变,因此便输出来了两个数字10。
这种问题的造成是由于变量counter读与写操作的不连续性(非原子性)造成的,根据邻接区的定义,我们将这组对counter读与写的代码成为临界区。解决这种临界区的竞争问题的可行方案是将邻接区程序片段改造成原子操作,比如,可以使用互斥锁改造临界区为(伪)原子操作。之所以称之为伪原子操作,是因为一个进程或线程进入邻接区期间,其它进程与线程仍然可能获取cpu执行时间,但互斥锁操作的原子性足可以保证同一时间只能有一个进程或线程进入临界区执行。
使用互斥锁的代码以及运行结果。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 #include <semaphore.h> 5 6 int counter=10; 7 pthread_mutex_t mutex; 8 9 void *mythread(); 10 11 int main(void) 12 { 13 void *ret1,*ret2; 14 pthread_t t1,t2; 15 16 pthread_mutex_init(&mutex,null); 17 18 pthread_create(&t1,null,&mythread,null); 19 pthread_create(&t2,null,&mythread,null); 20 pthread_join(t1,&ret1); 21 pthread_join(t2,&ret2); 22 23 pthread_mutex_destroy(&mutex); 24 25 return 0; 26 } 27 28 void *mythread() 29 { 30 31 while(1){ 32 pthread_mutex_lock(&mutex);//上锁 33 if(counter>0){ 34 printf("%d\n",counter); 35 counter--; 36 } 37 if(counter==0){ 38 pthread_mutex_unlock(&mutex);//开锁 39 break; 40 } 41 pthread_mutex_unlock(&mutex);//开锁 42 } 43 44 pthread_exit("finished\n"); 45 }
3. 原子操作的意义
从上面的例子可以看出,原子操作的原子性对于解决同步问题和避免竞争条件是绝对必要的。
三. 原子操作实现的硬件支持
互斥与同步问题可以使用诸如互斥锁与信号量之类的方案解决,那么互斥锁与信号量本身操作的原子性又是如何保证的呢?
1. 原子指令
某些计算机中,特别是哪些设计为多处理器的计算机,支持类似tsl、xchg的指令。xchg的功能:交换指令xchg是两个寄存器,寄存器和内存变量之间内容的交换指令,两个操作数的数据类型要相同,可以是一个字节,也可以四一个字,也可以是双字。xchg指令是对数据进行读写操作的原子指令,读写完成前无法中断,tsl指令类似。通过诸如xchg这类可同时对数据进行读写操作的原子指令,可在软件层面实现更多的原子操作。注意:xchg无法处理多cpu下的同步与互斥问题,多个cpu可以同时执行xchg指令读取相同的数据。执行tsl指令的cpu将锁住内存总线,以禁止其它cpu在本指令结束之前访问内存,可以处理多cpu情形下的同步与互斥问题。
2. 中断屏蔽
中断屏蔽可以保证cpu执行一段指令(开、关中断指令间的所有指令)结束前不会被打断,保证了原子性。应该注意到将中断屏蔽的权限交给用户程序将会是非常可怕的,时钟中断被屏蔽可让用户程序永远占有cpu。同时,多cpu的情形下,中断屏蔽无法保证其它cpu不访问内存。因此,中断屏蔽仅适合单cpu情形下的内核代码解决互斥与同步问题。