其实在操作系统中,都存在着多进程与多线程来优化处理整个操作系统的逻辑,不仅仅在操作系统,我们所编写的程序中都一样存在着各种地方使用多进程多线程,虽然多进程多线程看着很好很强大,但是总存在着非原子性情况下的执行顺序确定操作。

    所以就出现了信号量,条件变量,互斥锁等各种确保多线程/进程的执行顺序。

信号量的可以查看:

    Linux进程间通信:信号量

    Linux:生产者与消费者模式

       

互斥体(互斥锁)

      互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。
  任何线程在进入临界区之前,必须获取(acquire)与此区域相关联的互斥体的所有权。如果已有另一线程拥有了临界区的互斥体,其他线程就不能再进入其中。这些线程必须等待,直到当前的属主线程释放(release)该互斥体。
  什么时候需要使用互斥体呢?互斥体用于保护共享的易变代码,也就是,全局或静态数据。这样的数据必须通过互斥体进行保护,以防止它们在多个线程同时访问时损坏。

以上转自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html

   关于线程的相关使用:Linux之线程:同步与互斥

三、自旋锁

      自旋锁它是为为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

 

 

自旋锁一般原理

跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:死锁和过多占用cpu资源。

 

 

自旋锁适用情况

自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点:自旋锁不能递归使用

 

 

 

关于自旋锁的定义以及相应的API

自旋锁定义:  linux/Spinlock.h

typedef struct spinlock {
          union { //联合
             struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))             struct{
                     u8 __padding[LOCK_PADSIZE];                     struct lockdep_map dep_map;
             };#endif
         };
} spinlock_t;

 定义和初始化

spinlock_t my_lock = SPIN_LOCK_UNLOCKED; 
 spin_lock_init(spinlock_t *);

 

自旋锁操作:

//加锁一个自旋锁函数
void spin_lock(spinlock_t *lock);                                   //获取指定的自旋锁void 
spin_lock_irq(spinlock_t *lock);                               //禁止本地中断获取指定的锁
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);      //保存本地中断的状态,禁止本地中断,并获取指定的锁
void spin_lock_bh(spinlock_t *lock)                                 //安全地避免死锁, 而仍然允许硬件中断被服务
//释放一个自旋锁函数
void spin_unlock(spinlock_t *lock);                                 //释放指定的锁
void spin_unlock_irq(spinlock_t *lock);                             //释放指定的锁,并激活本地中断
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags); //释放指定的锁,并让本地中断恢复到以前的状态
void spin_unlock_bh(spinlock_t *lock);                              //对应于spin_lock_bh
//非阻塞锁
int spin_trylock(spinlock_t *lock);                  //试图获得某个特定的自旋锁,如果该锁已经被争用,该方法会立刻返回一个非0值,
                                                     //而不会自旋等待锁被释放,如果成果获得了这个锁,那么就返回0.
 int spin_trylock_bh(spinlock_t *lock);             //这些函数成功时返回非零( 获得了锁 ), 否则 0. 没有"try"版本来禁止中断.
 //其他
 int spin_is_locked(spinlock_t *lock);               //和try_lock()差不多

四、信号量、互斥体和自旋锁的区别

 

信号量/互斥体和自旋锁的区别

信号量/互斥体允许进程睡眠属于睡眠锁,自旋锁则不允许调用者睡眠,而是让其循环等待,所以有以下区别应用 
    1)、信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因而自旋锁适合于保持时间非常短的情况
    2)、自旋锁可以用于中断,不能用于进程上下文(会引起死锁)。而信号量不允许使用在中断中,而可以用于进程上下文
    3)、自旋锁保持期间是抢占失效的,自旋锁被持有时,内核不能被抢占,而信号量和读写信号量保持期间是可以被抢占
   
另外需要注意的是
     1)、信号量锁保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区,因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一进程企图获取本自旋锁,死锁就会发生。
     2)、在你占用信号量的同时不能占用自旋锁,因为在你等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。

 

 信号量和互斥体之间的区别

 

概念上的区别:     

      信号量:是进程间(线程间)同步用的,一个进程(线程)完成了某一个动作就通过信号量告诉别的进程(线程),别的进程(线程)再进行某些动作。有二值和多值信号量之分。

     互斥锁:是线程间互斥用的,一个线程占用了某一个共享资源,那么别的线程就无法访问,直到这个线程离开,其他的线程才开始可以使用这个共享资源。可以把互斥锁看成二值信号量。  

 

上锁时:

     信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一。一句话,信号量的value>=0。

     互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源。如果没有锁,获得资源成功,否则进行阻塞等待资源可用。一句话,线程互斥锁的vlaue可以为负数。  

 

使用场所:

     信号量主要适用于进程间通信,当然,也可用于线程间通信。而互斥锁只能用于线程间通信

    以上转自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html

读写锁:

    在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢?有,那就是读写锁。

    读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。

读写锁相关的操作函数:

wKioL1csT_7Av9U3AABM8SILEe8033.png

读写锁操纵:

wKioL1csUZCSp7Z_AAA8Iyc4p2o381.png

wKiom1csULiTy5nFAAA8UjOnmZ0448.png

wKioL1csUZHS3T3EAAAnjUPVLVM394.png

函数整体线程之前的讲解比较像,所以直接上代码吧:

//阻塞读写锁模式:
#include<stdio.h>
#include<pthread.h>

int g_val = 0;

pthread_rwlock_t rw_lock;

void* reader(void* arg)
{
	//阻塞读写锁。
	while(1)
	{
		pthread_rwlock_rdlock(&rw_lock);
		printf("g_val: %d\n",g_val);
		pthread_rwlock_unlock(&rw_lock);	
	}
}

void* writer(void* arg)
{
	while(1)
	{
		sleep(1);
		pthread_rwlock_wrlock(&rw_lock);
		g_val++;
		pthread_rwlock_unlock(&rw_lock);
	}
}

int main()
{
	pthread_t tid1,tid2;
	pthread_rwlock_init(&rw_lock,NULL);
	pthread_create(&tid1,NULL,reader,NULL);
	pthread_create(&tid2,NULL,writer,NULL);
	pthread_rwlock_destroy(&rw_lock);
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	return 0;
}

运行结果:

运行结果就是写者每隔一秒进行一次写,读者一直在读。结果截图太长了,所以就不截图了。


非阻塞模式:

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

int g_val = 0;
pthread_rwlock_t rw_lock;

void* reader(void* arg)
{
	pthread_detach(pthread_self());
	//非阻塞读写锁。
	while(1)
	{
		if(pthread_rwlock_tryrdlock(&rw_lock)!= 0){
			printf("writer is wirting,reader waiting...\n");
		}
		else{
			printf("reader is:%u,g_val: %d\n",pthread_self(),g_val);
			pthread_rwlock_unlock(&rw_lock);
		}	
		sleep(2);
	}
}

void* writer(void* arg)
{	
	pthread_detach(pthread_self());
	while(1)
	{
		if(pthread_rwlock_trywrlock(&rw_lock) != 0){
			printf("reader is reading,writer waiting\n");
			sleep(1);
		}
		else{
			g_val++;
			printf("writer is :%u,writer val is :%d\n",pthread_self(),g_val);
			pthread_rwlock_unlock(&rw_lock);
		}
		sleep(1);
	}
}

int main()
{
	pthread_rwlock_init(&rw_lock,NULL);
	pthread_t id;
	int i ;
	for(i = 0;i<2;++i)
	{
		pthread_create(&id, NULL,writer,NULL);
	}
	for(i = 0;i < 3;++i)
	{
		pthread_create(&id,NULL,reader,NULL);
	}
	sleep(100);
	return 0;
}

运行结果:

wKiom1csUiCj0lhMAABgcbTev9k908.png