互斥量,生产者消费者

如果不需要信号量的计数能力,有时可以使用信号量的一个简化版本,称为互斥量(mutex)。互斥量仅仅适用于管理共享资源或一小段代码。由于互斥量在实现时既容易又有效,这使得互斥量在实现用户空间线程包时非常有用。

互斥量是一个可以处于两态之一的变量:解锁和加锁。这样,只需要一个二进制位表示它,不过实际上,常常使用一个整型量,0表示解锁,而其他所有的值则表示加锁。互斥量使用两个过程。当一个线程(或进程)需要访问临界区时,它调用mutex_lock。如果该互斥量当前是解锁的(即临界区可用),此调用成功,调用线程可以自由进入该临界区。

另一方面,如果该互斥量已经加锁,调用线程被阻塞,直到在临界区中的线程完成并调用mutex_unlock。如果多个线程被阻塞在该互斥量上,将随机选择一个线程并允许它获得锁。

由于互斥量非常简单,所以如果有可用的TSL或XCHG指令,就可以很容易地在用户空间中实现它们。用于用户级线程包的mutex_lock和mutex_unlock代码如图2-29所示。XCHG解法本质上是相同的。

 

图2-29   mutex_lock和mutex_unlock的实现

mutex_lock 的代码与图2-25中enter_region的代码很相似,但有一个关键的区别。当enter_region进入临界区失败时,它始终重复测试锁(忙等待)。实际上,由于时钟超时的作用,会调度其他进程运行。这样迟早拥有锁的进程会进入运行并释放锁

在(用户)线程中,情形有所不同,因为没有时钟停止运行时间过长的线程。结果是通过忙等待的方式来试图获得锁的线程将永远循环下去,决不会得到锁,因为这个运行的线程不会让其他线程运行从而释放锁。

以上就是enter_region和mutex_lock 的差别所在。在后者取锁失败时,它调用thread_yield将CPU放弃给另一个线程。这样,就没有忙等待在该线程下次运行时,它再一次对锁进行测试

由于thread_yield只是在用户空间中对线程调度程序的一个调用,所以它的运行非常快捷。这样,mutex_lock和mutex_unlock都不需要任何内核调用。通过使用这些过程,用户线程完全可以实现在用户空间中的同步,这些过程仅仅需要少量的指令

上面所叙述的互斥量系统是一套调用框架。对于软件来说,总是需要更多的特性,而同步原语也不例外。例如,有时线程包提供一个调用mutex_trylock,这个调用或者获得锁或者返回失败码,但并不阻塞线程。这就给了调用线程一个灵活性,用以决定下一步做什么,是使用替代办法还只是等待下去。

在用户级线程包中,多个线程访问同一个互斥量是没有问题的,因为所有的线程都在一个公共地址空间中操作。但是,对于大多数早期解决方案,诸如Peterson算法和信号量等,都有一个未说明的前提,即这些多个进程至少应该访问一些共享内存,也许仅仅是一个字。如果进程有不连续的地址空间,如我们始终提到的,那么在Peterson算法、信号量或公共缓冲区中,它们如何共享turn变量呢?

有两种方案。第一种,有些共享数据结构,如信号量,可以存放在内核中,并且只能通过系统调用来访问。这种处理方式化解了上述问题。第二种,多数现代操作系统(包括UNIX和Windows)提供一种方法,让进程与其他进程共享其部分地址空间。在这种方法中,缓冲区和其他数据结构可以共享。在最坏的情形下,如果没有可共享的途径,则可以使用共享文件

如果两个或多个进程共享其全部或大部分地址空间,进程和线程之间的差别就变得模糊起来,但无论怎样,两者的差别还是有的。共享一个公共地址空间的两个进程仍旧有各自的打开文件、报警定时器以及其他一些单个进程的特性,而在单个进程中的线程,则共享进程全部的特性。另外,共享一个公共地址空间的多个进程决不会拥有用户级线程的效率,这一点是不容置疑的,因为内核还同其管理密切相关。

Pthread中的互斥量

Pthread提供许多可以用来同步线程的函数。其基本机制是使用一个可以被锁定和解锁的互斥量来保护每个临界区。一个线程如果想要进入临界区,它首先尝试锁住相关的互斥量。如果互斥量没有加锁,那么这个线程可以立即进入,并且该互斥量被自动锁定以防止其他线程进入。如果互斥量已经被加锁,则调用线程被阻塞,直到该互斥量被解锁。如果多个线程在等待同一个互斥量,当它被解锁时,这些等待的线程中只有一个被允许运行并将互斥量重新锁定。这些互斥锁不是强制性的,而是由程序员来保证线程正确地使用它们。

与互斥量相关的主要函数调用如图2-30所示。就像所期待的那样,可以创建和撤销互斥量。实现它们的函数调用分别是pthread_mutex_initpthread_mutex_destroy。也可以通过pthread_mutex_lock给互斥量加锁,如果该互斥量已被加锁时,则会阻塞调用者。还有一个调用可以用来尝试锁住一个互斥量,当互斥量已被加锁时会返回错误代码而不是阻塞调用者。这个调用就是pthread_mutex_trylock。如果需要的话,该调用允许一个线程有效地忙等待。最后,pthread_mutex_unlock用来给一个互斥量解锁,并在一个或多个线程等待它的情况下正确地释放一个线程。互斥量也可以有属性,但是这些属性只在某些特殊的场合下使用。

  

除互斥量之外,pthread提供了另一种同步机制:条件变量。互斥量在允许或阻塞对临界区的访问上是很有用的,条件变量则允许线程由于一些未达到的条件而阻塞。绝大部分情况下这两种方法是一起使用的。

考虑一下生产者-消费者问题:一个线程将产品放在一个缓冲区内,由另一个线程将它们取出。如果生产者发现缓冲区中没有空槽可以使用了,它不得不阻塞起来直到有一个空槽可以使用。生产者使用互斥量可以进行原子性检查,而不受其他线程干扰。但是当发现缓冲区已经满了以后,生产者需要一种方法来阻塞自己并在以后被唤醒。这便是条件变量做的事了。

与条件变量相关的pthread调用如图2-31所示。有专门的调用用来创建和撤销条件变量。它们可以有属性,并且有不同的调用来管理它们(图中没有显示)。与条件变量相关的最重要的两个操作是pthread_cond_waitpthread_cond_signal。前者阻塞调用线程直到另一其他线程向它发信号(使用后一个调用)。当然,阻塞与等待的原因不是等待与发信号协议的一部分。被阻塞的线程经常是在等待发信号的线程去做某些工作、释放某些资源或是进行其他的一些活动。只有完成后被阻塞的线程才可以继续运行。条件变量允许这种等待与阻塞原子性地进行。当有多个线程被阻塞并等待同一个信号时,可以使用pthread_cond_broadcast调用。

  

条件变量与互斥量经常一起使用。这种模式用于让一个线程锁住一个互斥量,然后当它不能获得它期待的结果时等待一个条件变量最后另一个线程会向它发信号,使它可以继续执行pthread_cond_wait原子性地调用并解锁它持有的互斥量。由于这个原因,互斥量是参数之一。

条件变量(不像信号量)不会存在内存中如果将一个信号量传递给一个没有线程在等待的条件变量,那么这个信号就会丢失。程序员必须小心使用避免丢失信号。

图2-32展示了一个非常简单只有一个缓冲区的生产者-消费者问题。当生产者填满缓冲区时,它在生产下一个数据项之前必须等待,直到消费者清空了它。类似地,当消费者移走一个数据项时,它必须等待,直到生产者生产了另外一个数据项。使一个线程睡眠的语句应该总是要检查这个条件,以保证线程在继续执行前满足条件,因为线程可能已经因为一个UNIX信号或其他原因而被唤醒。

  
   

图2-32   利用线程解决生产者-消费者问题



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值