信号量和条件变量与互斥锁

来源:互联网 发布:3d2015历史开奖数据 编辑:程序博客网 时间:2024/06/08 20:22

1.信号量与互斥锁

信号量与普通整型变量的区别:

①信号量(semaphore)是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap) , signal(semap) ; 来进行访问;

②操作也被成为PV原语(P来源于Dutch proberen"测试",V来源于Dutch verhogen"增加"),而普通整型变量则可以在任何语句块中被访问; 

信号量与互斥锁之间的区别:

1. 互斥量用于线程的互斥,信号线用于线程的同步。  

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。  

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。  

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源  

2. 互斥量值只能为0/1,信号量值可以为非负整数。  

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。  

3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

1.1信号量 


信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。 


信号量可以分为几类:  
² 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。  

² 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。  

² 记录型信号量(record semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。  

信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态。  

计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。  

Semaphore可以被抽象为五个操作:  
- 创建 Create  

- 等待 Wait:  

线程等待信号量,如果值大于0,则获得,值减一;如果只等于0,则一直线程进入睡眠状态,知道信号量值大于0或者超时。  

-释放 Post  

执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程。  

-试图等待 TryWait  

如果调用TryWait,线程并不真正的去获得信号量,还是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功;否则返回失败。  

-销毁 Destroy  

信号量,是可以用来保护两个或多个关键代码段,这些关键代码段不能并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。如果关键代码段中没有任何线程,那么线程会立即进入该框图中的那个部分。一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。 动作\系统
 Win32
 POSIX
 
创建
 CreateSemaphore
 sem_init
 
等待
 WaitForSingleObject
 sem _wait
 
释放
 ReleaseMutex
 sem _post
 
试图等待
 WaitForSingleObject
 sem _trywait
 
销毁
 CloseHandle
 sem_destroy

1.2互斥量(Mutex)  


互斥量表现互斥现象的数据结构,也被当作二元信号灯。一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源。  
Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex主要的作用是用于互斥。Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。值为0, 表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待;值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0。  

Mutex可以被抽象为四个操作:  

- 创建 Create  

- 加锁 Lock  

- 解锁 Unlock  

- 销毁 Destroy  

Mutex被创建时可以有初始值,表示Mutex被创建后,是锁定状态还是空闲状态。在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁(系统一般会在第二次调用立刻返回)。也就是说,加锁和解锁这两个对应的操作,需要在同一个线程中完成。  

不同操作系统中提供的Mutex函数: 动作\系统
 Win32
 Linyx
 Solaris
 
创建
 CreateMutex
 pthread_mutex_init
 mutex_init
 
加锁
 WaitForSingleObject
 pthread_mutex_lock
 mutex_lock
 
解锁
 ReleaseMutex
 pthread_mutex_unlock
 mutex_unlock
 
销毁
 CloseHandle
 pthread_mutex_destroy
 mutex_destroy


2.线程同步:何时互斥锁不够,还需要条件变量?

 

假设有共享的资源sum,与之相关联的mutex 是lock_s.假设每个线程对sum的操作很简单的,与sum的状态无关,比如只是sum++.那么只用mutex足够了.程序员只要确保每个线程操作前,取得lock,然后sum++,再unlock即可.每个线程的代码将像这样

add() 
{  
     pthread_mutex_lock(lock_s); 
    sum++; 
    pthread_mutex_unlock(lock_s); 
}


如果操作比较复杂,假设线程t0,t1,t2的操作是sum++,而线程t3则是在sum到达100的时候,打印出一条信息,并对sum清零. 这种情况下,如果只用mutex, 则t3需要一个循环,每个循环里先取得lock_s,然后检查sum的状态,如果sum>=100,则打印并清零,然后unlock.如果sum& amp; amp; amp; lt;100,则unlock,并sleep()本线程合适的一段时间. 
这个时候,t0,t1,t2的代码不变,t3的代码如下

print()  {     while(1)     {  pthread_mutex_lock(lock_s);  if(sum<100)  {  printf(“sum reach 100!”);  pthread_mutex_unlock(lock_s);  }  else {  pthread_mutex_unlock(lock_s);  my_thread_sleep(100);  return OK;  }      }  }

这种办法有两个问题 
1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,只是lock和unlock.这浪费了CPU处理时间. 
2) 为了节省CPU处理时间,t3会在探测到sum没到达100的时候sleep()一段时间.这样却又带来另外一个问题,亦即t3响应速度下降.可能在sum到达200的时候,t4才会醒过来. 
3) 这样,程序员在设置sleep()时间的时候陷入两难境地,设置得太短了节省不了资源,太长了又降低响应速度.真是难办啊!


这个时候,condition variable内裤外穿,从天而降,拯救了焦头烂额的你. 

你首先定义一个condition variable. 
    pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER; 
t0,t1,t2的代码只要后面加两行,像这样

add() { pthread_mutex_lock(lock_s); sum++; 
pthread_mutex_unlock(lock_s);  
      if(sum>=100) 
     pthread_cond_signal(&cond_sum_ready);
}
而t3的代码则是
print { pthread_mutex_lock(lock_s); 
while(sum < 100)//为何还要判断,因为可能被其它线程改变了值。
{pthread_cond_wait(&cond_sum_ready, &lock_s); //线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,则调用该线程将阻塞,直到该互斥锁变为可用为止
printf(“sum is over 100!”); sum=0; pthread_mutex_unlock(lock_s); return OK; }
注意: 1) 在thread_cond_wait()之前,必须先lock相关联的mutex, 因为假如目标条件未满足,pthread_cond_wait()实际上会unlock该mutex, 然后block,在目标条件满足后再重新lock该mutex, 然后返回. 2)pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上 的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 

3. 定时等待和广播

  通常pthread_cond_signal只是唤醒等待在相应条件变量上的一个线程,在某些情况下需要唤醒多个线程(例如读写者问题),可以调用pthread_cond_broadcast唤醒阻塞在相应条件变量上的所有线程。pthread_cond_timewait允许线程就阻塞时间设置一个限制值。API如下:

pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex, const struct timespec *abstime);


感觉还有些细节没有理清,待之后再整理补上。
原创粉丝点击