设为首页 加入收藏

TOP

10.2.2 自旋互斥体
2013-10-07 15:05:29 来源: 作者: 【 】 浏览:65
Tags:10.2.2 自旋

10.2.2  自旋互斥体

还有一种特殊的基于轮询的进程内互斥体,这通常是一种糟糕的实践。简单地说,轮询就是通过反复不断地测试来等待某个条件的改变,像这样:

  1. int g_flag;  
  2.  
  3. // 等待线程  
  4. while(0 == g_flag)  
  5. {}  
  6. . . . // 现在做我们想做的事吧  

这种东西会吞噬处理器时钟周期,因为跟那个改变标志变量从而允许轮询线程继续执行的线程相比,轮询线程通常2被给予同样的优先级。轮询是个糟糕的主意,通常标志着使用它的人是个刚接触多线程的菜鸟,或者等着拿解雇费的萎瓜。

然而,某些场合则非常合适于采用自旋(spinning)。让我们看看下面自旋互斥体的实现,这是UNIXSTL3中的spin_mutex类(如程序清单10.2):

程序清单10.2

  1. class spin_mutex  
  2. {  
  3. public:  
  4.   explicit spin_mutex(sint32_t *p = NULL)  
  5.     : m_spinCount((NULL != p)   p : &m_internalCount)  
  6.     , m_internalCount(0)  
  7.   {}  
  8.   void lock()  
  9.   {  
  10.     for(; 0 != atomic_write(m_spinCount, 1); sched_yield())  
  11.     {}  
  12.   }  
  13.   void unlock()  
  14.   {  
  15.     atomic_set(m_spinCount, 0);  
  16.   }  
  17. // 成员  
  18. private:  
  19.   sint32_t  *m_spinCount;  
  20.   sint32_t  m_internalCount;  
  21. // 声明但不予实现  
  22. private:  
  23.     spin_mutex(class_type const &rhs);  
  24.     spin_mutex &operator =(class_type const &rhs);  
  25. };  

自旋互斥体的工作机制相当简单。当lock()被调用时,执行一次原子写操作,将自旋变量*m_spinCount(整型)置为1。如果它原先的值为0,就意味着调用者是设置它的第一个线程,从而"获取"该互斥体,然后该方法(lock())返回。但如果原先的值为1,那就意味着调用者被另一个线程拒之门外1,无法获得该互斥体,于是接着调用PTHREAD函数sched_yield(),将执行机会让给其他线程。以后当它被再次唤醒时,它就会再次尝试获取该互斥体。这个过程不断重复,直到获取成功,从而该互斥体的所有权就得到了锁定。

当获取了互斥体的线程调用unlock()时,自旋变量*m_spinCount会被重置为0,从而允许其他线程再次获取该互斥体。由于该类可以基于一个外部的自旋变量来创建,因而引入了成员m_internalCount,使得构造函数看上去有一点复杂,不过这在某些特定的场合可能会非常有用(我们将在第11章和第31章中看到这一点)。

然而,当出现激烈的竞争情况时,自旋互斥体就不再是好的解决方案了。不过,在出现竞争的可能性很低并且获取/释放同步机制的开销也不高的情况下,使用自旋互斥体是行之有效的方案。由于它们可能导致高昂的开销,所以我倾向于仅将它们用在初始化动作中,这种情况下竞争极其罕见,但理论上仍是可能的,你心里要有数。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇10.1.1 操作系统函数 下一篇10.4.1 synchronized

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: