📄 posix 线程详解 - 菜鸟成长轨迹 - csdnblog.htm
字号:
<STRONG>不会<BR></STRONG>释放用来存储 pthread_mutex_t
的内存。释放自己的内存完全取决于您。还必须注意一<BR>点,pthread_mutex_init() 和 pthread_mutex_destroy()
成功时都返回零。 </P>
<H2><A name=N10113><SPAN class=atitle>使用调用:锁定</SPAN></A></H2><PRE class=displaycode>pthread_mutex_lock(pthread_mutex_t *mutex)</PRE>
<P>pthread_mutex_lock()
接受一个指向互斥对象的指针作为参数以将其锁定。如果碰巧已经<BR>锁定了互斥对象,调用者将进入睡眠状态。函数返回时,将唤醒调用者(显然)并且调用<BR>者还将保留该锁。函数调用成功时返回零,失败时返回非零的错误代码。</P><PRE class=displaycode>pthread_mutex_unlock(pthread_mutex_t *mutex)</PRE>
<P>pthread_mutex_unlock() 与 pthread_mutex_lock()
相配合,它把线程已经加锁的互斥对<BR>象解锁。始终应该尽快对已加锁的互斥对象进行解锁(以提高性能)。并且绝对不要对您<BR>未保持锁的互斥对象进行解锁操作(否则,pthread_mutex_unlock()
调用将失败并带一个<BR>非零的 EPERM 返回值)。</P>
<P></P><PRE class=displaycode>pthread_mutex_trylock(pthread_mutex_t *mutex)<BR></PRE>
<P>当线程正在做其它事情的时候(由于互斥对象当前是锁定的),如果希望锁定互斥对象,<BR>这个调用就相当方便。调用
pthread_mutex_trylock()
时将尝试锁定互斥对象。如果互斥<BR>对象当前处于解锁状态,那么您将获得该锁并且函数将返回零。然而,如果互斥对象已锁<BR>定,这个调用也不会阻塞。当然,它会返回非零的
EBUSY 错误值。然后可以继续做其它<BR>事情,稍后再尝试锁定。</P>
<H2><A name=N1012E><SPAN class=atitle>等待条件发生</SPAN></A></H2>
<P>互斥对象是线程程序必需的工具,但它们并非万能的。例如,如果线程正在等待共享数据<BR>内某个条件出现,那会发生什么呢?代码可以反复对互斥对象锁定和解锁,以检查值的任<BR>何变化。同时,还要快速将互斥对象解锁,以便其它线程能够进行任何必需的更改。这是<BR>一种非常可怕的方法,因为线程需要在合理的时间范围内频繁地循环检测变化。</P>
<P>在每次检查之间,可以让调用线程短暂地进入睡眠,比如睡眠三秒钟,但是因此线程代码<BR>就无法最快作出响应。真正需要的是这样一种方法,当线程在等待满足某些条件时使线程<BR>进入睡眠状态。一旦条件满足,还需要一种方法以唤醒因等待满足特定条件而睡眠的线程。<BR>如果能够做到这一点,线程代码将是非常高效的,并且不会占用宝贵的互斥对象锁。这正是<BR>POSIX
条件变量能做的事!</P>
<P>而 POSIX
条件变量将是我下一篇文章的主题,其中将说明如何正确使用条件变量。到那时,<BR>您将拥有了创建复杂线程程序所需的全部资源,那些线程程序可以模拟工作人员、装配线<BR>等等。既然您已经越来越熟悉线程,我将在下一篇文章中加快进度。这样,在下一篇文章<BR>的结尾就能放上一个相对复杂的线程程序。说到等到条件产生,下次再见!</P>
<H1>第三部分:使用条件变量提高效率</H1>
<BLOCKQUOTE>本文是 POSIX 线程三部曲系列的最后一部分,Daniel 将详细讨论如何使用条件变<BR>量。条件变量是 POSIX
线程结构,可以让您在遇到某些条件时“唤醒”线程。可<BR>以将它们看作是一种线程安全的信号发送。Daniel
使用目前您所学到的知识实现了<BR>一个多线程工作组应用程序,本文将围绕着这一示例而进行讨论。</BLOCKQUOTE><!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
<H2><A name=1><SPAN class=atitle>条件变量详解</SPAN></A></H2>
<P>在 <A
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread2/index.html"><FONT
color=#5c81a7>上一篇文章</FONT></A>结束时,我描述了一个比较特殊的难题:如果线程正在等待某个特定条件发<BR>生,它应该如何处理这种情况?它可以重复对互斥对象锁定和解锁,每次都会检查共享数<BR>据结构,以查找某个值。但这是在浪费时间和资源,而且这种繁忙查询的效率非常低。解<BR>决这个问题的最佳方法是使用
pthread_cond_wait() 调用来等待特殊条件发生。 </P>
<P>了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心,也<BR>是最难以理解的部分。</P>
<P>首先,让我们考虑以下情况:线程为查看已链接列表而锁定了互斥对象,然而该列表恰巧<BR>是空的。这一特定线程什么也干不了 --
其设计意图是从列表中除去节点,但是现在却没有<BR>节点。因此,它只能:</P>
<P>锁定互斥对象时,线程将调用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait()
<BR>调用相当复杂,因此我们每次只执行它的一个操作。</P>
<P>pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁(于是其它线程可以修改<BR>已链接列表),并等待条件 mycond
发生(这样当 pthread_cond_wait()
接收到另一个<BR>线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链<BR>接列表,可能还会添加项。</P>
<P>此时,pthread_cond_wait()
调用还未返回。对互斥对象解锁会立即发生,但等待条件mycond<BR>通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU
周期。这正是<BR>我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费<BR>CPU 时间的繁忙查询。从线程的角度来看,它只是在等待
pthread_cond_wait() 调用返回。</P>
<P>现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并对已链接列表添<BR>加了一项。在对互斥对象解锁之后,2
号线程会立即调用函数pthread_cond_broadcast(&mycond)。<BR>此操作之后,2 号线程将使所有等待 mycond
条件变量的线程立即苏醒。这意味着第一个<BR>线程(仍处于 pthread_cond_wait() 调用中)现在将苏醒。</P>
<P>现在,看一下第一个线程发生了什么。您可能会认为在2 号线程调用pthread_cond_broadcast(&mymutex) <BR>之后,1
号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() <BR>将执行最后一个操作:重新锁定
mymutex。一旦 pthread_cond_wait() 锁定了互斥对象,<BR>那么它将返回并允许 1
号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。</P>
<H2><A name=2><SPAN class=atitle>停止并回顾!</SPAN></A></H2>
<P>那个过程非常复杂,因此让我们先来回顾一下。第一个线程首先调用:</P>
<P></P><PRE class=displaycode> pthread_mutex_lock(&mymutex);</PRE>
<P>然后,它检查了列表。没有找到感兴趣的东西,于是它调用:</P>
<P></P><PRE class=displaycode> pthread_cond_wait(&mycond, &mymutex);</PRE>
<P>然后,pthread_cond_wait() 调用在返回前执行许多操作:</P><PRE class=displaycode> pthread_mutex_unlock(&mymutex);<BR></PRE>
<P>它对 mymutex 解锁,然后进入睡眠状态,等待 mycond 以接收 POSIX
线程“信号”。一旦接<BR>收到“信号”(加引号是因为我们并不是在讨论传统的 UNIX 信号,而是来自 pthread_cond_signal() <BR>或
pthread_cond_broadcast() 调用的信号),它就会苏醒。但 pthread_cond_wait() 没有立即<BR>返回 --
它还要做一件事:重新锁定 mutex: <BR></P>
<P></P><PRE class=displaycode> pthread_mutex_lock(&mymutex);</PRE>
<P>pthread_cond_wait() 知道我们在查找 mymutex “背后”的变化,因此它继续操作,为我们锁<BR>定互斥对象,然后才返回。</P>
<H2><A name=3><SPAN class=atitle>pthread_cond_wait() 小测验</SPAN></A></H2>
<P>现在已回顾了 pthread_cond_wait() 调用,您应该了解了它的工作方式。应该能够叙述pthread_cond_wait()
<BR>依次执行的所有操作。尝试一下。如果理解了
pthread_cond_wait(),其余部分就相当容易,<BR>因此请重新阅读以上部分,直到记住为止。好,读完之后,能否告诉我在调用
pthread_cond_wait() <BR>之<EM>前</EM>,互斥对象必须处于什么状态?pthread_cond_wait()
调用返回之后,互斥对象处于什么状态?<BR>这两个问题的答案都是“锁定”。既然已经完全理解了 pthread_cond_wait()
调用,现在来继<BR>续研究更简单的东西 -- 初始化和真正的发送信号和广播进程。到那时,我们将会对包含了多线<BR>程工作队列的 C
代码了如指掌。<BR></P>
<H2><A name=4><SPAN class=atitle>初始化和清除</SPAN></A></H2>
<P>条件变量是一个需要初始化的真实数据结构。以下就初始化的方法。首先,定义或分配一个条件<BR>变量,如下所示:</P><PRE class=displaycode> pthread_cond_t mycond;</PRE>
<P>然后,调用以下函数进行初始化:</P>
<P></P><PRE class=displaycode> pthread_cond_init(&mycond,NULL);</PRE>
<P>瞧,初始化完成了!在释放或废弃条件变量之前,需要毁坏它,如下所示:</P>
<P></P><PRE class=displaycode> pthread_cond_destroy(&mycond);</PRE>
<P>很简单吧。接着讨论 pthread_cond_wait() 调用。</P>
<H2><A name=5><SPAN class=atitle>等待</SPAN></A></H2>
<P>一旦初始化了互斥对象和条件变量,就可以等待某个条件,如下所示:</P><PRE class=displaycode> pthread_cond_wait(&mycond, &mymutex);</PRE>
<P>请注意,代码在逻辑上应该包含 mycond 和
mymutex。一个特定条件只能有一个互斥对象,<BR>而且条件变量应该表示互斥数据“内部”的一种特殊的条件更改。一个互斥对象可以用许多条<BR>件变量(例如,cond_empty、cond_full、cond_cleanup),但每个条件变量只能有一个互斥<BR>对象。</P>
<H2><A name=6><SPAN class=atitle>发送信号和广播</SPAN></A></H2>
<P>对于发送信号和广播,需要注意一点。如果线程更改某些共享数据,而且它想要唤醒所有正在<BR>等待的线程,则应使用
pthread_cond_broadcast 调用,如下所示:</P>
<P></P><PRE class=displaycode> pthread_cond_broadcast(&mycond);</PRE>
<P>在某些情况下,活动线程只需要唤醒第一个正在睡眠的线程。假设您只对队列添加了一个工作<BR>作业。那么只需要唤醒一个工作程序线程(再唤醒其它线程是不礼貌的!):</P><PRE class=displaycode> pthread_cond_signal(&mycond);</PRE>
<P>此函数只唤醒一个线程。如果 POSIX
线程标准允许指定一个整数,可以让您唤醒一定数量的<BR>正在睡眠的线程,那就更完美了。但是很可惜,我没有被邀请参加会议。<BR></P>
<H2><A name=7><SPAN class=atitle>工作组</SPAN></A></H2>
<P>我将演示如何创建多线程工作组。在这个方案中,我们创建了许多工作程序线程。每个线程<BR>都会检查
wq(“工作队列”),查看是否有需要完成的工作。如果有需要完成的工作,那<BR>么线程将从队列中除去一个节点,执行这些特定工作,然后等待新的工作到达。</P>
<P>与此同时,主线程负责创建这些工作程序线程、将工作添加到队列,然后在它退出时收集所<BR>有工作程序线程。您将会遇到许多 C 代码,好好准备吧!</P>
<H2><A name=8><SPAN class=atitle>队列</SPAN></A></H2><BR>
<P>需要队列是出于两个原因。首先,需要队列来保存工作作业。还需要可用于跟踪已终止线程<BR>的数据结构。还记得前几篇文章(请参阅本文结尾处的 <A
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread3/index.html#resources"><FONT
color=#996699>参考资料</FONT></A>)中,我曾提到过需要使用<BR>带有特定进程标识的 pthread_join
吗?使用“清除队列”(称作
"cq")可以解决无法等待<BR><EM>任何</EM>已终止线程的问题(稍后将详细讨论这个问题)。以下是标准队列代码。将此代码保存<BR>到文件
queue.h 和 queue.c: <BR><BR><A name=N100F7><STRONG>queue.h</STRONG></A></P><PRE class=displaycode>/* queue.h<BR>** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.<BR>** Author: Daniel Robbins<BR>** Date: 16 Jun 2000<BR>*/<BR>typedef struct node {<BR> struct node *next;<BR>} node;<BR>typedef struct queue {<BR> node *head, *tail; <BR>} queue;<BR>void queue_init(queue *myroot);<BR>void queue_put(queue *myroot, node *mynode);<BR>node *queue_get(queue *myroot);<BR></PRE>
<P><A name=N10103><STRONG>queue.c</STRONG></A></P><PRE class=displaycode>/* queue.c<BR>** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.<BR>** Author: Daniel Robbins<BR>** Date: 16 Jun 2000<BR>**<BR>** This set of queue functions was originally thread-aware. I<BR>** redesigned the code to make this set of queue routines<BR>** thread-ignorant (just a generic, boring yet very fast set of queue<BR>** routines). Why the change? Because it makes more sense to have<BR>** the thread support as an optional add-on. Consider a situation<BR>** where you want to add 5 nodes to the queue. With the<BR>** thread-enabled version, each call to queue_put() would<BR>** automatically lock and unlock the queue mutex 5 times -- that's a<BR>** lot of unnecessary overhead. However, by moving the thread stuff<BR>** out of the queue routines, the caller can lock the mutex once at<BR>** the beginning, then insert 5 items, and then unlock at the end.<BR>** Moving the lock/unlock code out of the queue functions allows for<BR>** optimizations that aren't possible otherwise. It also makes this<BR>** code useful for non-threaded applications.<BR>**<BR>** We can easily thread-enable this data structure by using the<BR>** data_control type defined in control.c and control.h. */<BR>#include <stdio.h><BR>#include "queue.h"<BR>void queue_init(queue *myroot) {<BR> myroot->head=NULL;<BR> myroot->tail=NULL;<BR>}<BR>void queue_put(queue *myroot,node *mynode) {<BR> mynode->next=NULL;<BR> if (myroot->tail!=NULL)<BR> myroot->tail->next=mynode;<BR> myroot->tail=mynode;<BR> if (myroot->:head==NULL)<BR> myroot->head=mynode;<BR>}<BR>node *queue_get(queue *myroot) {<BR> //get from root<BR> node *mynode;<BR> mynode=myroot->head;<BR> if (myroot->head!=NULL)<BR> myroot->head=myroot->head->next;<BR> return mynode;<BR>}</PRE>
<H2><A name=9><SPAN class=atitle>data_control 代码</SPAN></A><BR></H2>
<P>我编写的并不是线程安全的队列例程,事实上我创建了一个“数据包装”或“控制”结构,<BR>它可以是任何线程支持的数据结构。看一下
control.h:<BR><BR><A name=N10116><STRONG>control.h</STRONG></A></P><PRE class=displaycode>#include <BR>typedef struct data_control {<BR> pthread_mutex_t mutex;<BR> pthread_cond_t cond;<BR> int active;<BR>} data_control;</PRE>
<P><BR>现在您看到了 data_control 结构定义,以下是它的视觉表示:<BR><A name=10><STRONG>所使用的
data_control 结构</STRONG></A><BR><IMG alt=""
src="POSIX 线程详解 - 菜鸟成长轨迹 - CSDNBlog.files/control.gif" valign="top">
<BR></P><BR>
<P>图像中的锁代表互斥对象,它允许对数据结构进行互斥访问。黄色的星代表条件变量,它<BR>可以睡眠,直到所讨论的数据结构改变为止。on/off 开关表示整数
"active",它告诉线程此<BR>数据是否是活动的。在代码中,我使用整数 active 作为标志,告诉工作队列何时应该关闭。<BR>以下是
control.c:</P>
<P><A name=N10136><STRONG>control.c</STRONG></A></P><PRE class=displaycode>/* control.c<BR>** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.<BR>** Author: Daniel Robbins<BR>** Date: 16 Jun 2000<BR>**<BR>** These routines provide an easy way to make any type of<BR>** data-structure thread-aware. Simply associate a data_control<BR>** structure with the data structure (by creating a new struct, for<BR>** example). Then, simply lock and unlock the mutex, or<BR>** wait/signal/broadcast on the condition variable in the data_control<BR>** structure as needed.<BR>**<BR>** data_control structs contain an int called "active". This int is<BR>** intended to be used for a specific kind of multithreaded design,<BR>** where each thread checks the state of "active" every time it locks<BR>** the mutex. If active is 0, the thread knows that instead of doing<BR>** its normal routine, it should stop itself. If active is 1, it<BR>** should continue as normal. So, by setting active to 0, a<BR>** controlling thread can easily inform a thread work crew to shut<BR>** down instead of processing new jobs. Use the control_activate()<BR>** and control_deactivate() functions, which will also broadcast on<BR>** the data_control struct's condition variable, so that all threads<BR>** stuck in pthread_cond_wait() will wake up, have an opportunity to<BR>** notice the change, and then terminate.<BR>*/<BR>#include "control.h"<BR>int control_init(data_control *mycontrol) {<BR> int mystatus;<BR> if (pthread_mutex_init(&(mycontrol->mutex),NULL))<BR> return 1;<BR> if (pthread_cond_init(&(mycontrol->cond),NULL))<BR> return 1;<BR> mycontrol->active=0;<BR> return 0;<BR>}<BR>int control_destroy(data_control *mycontrol) {<BR> int mystatus;<BR> if (pthread_cond_destroy(&(mycontrol->cond)))<BR> return 1;<BR> if (pthread_cond_destroy(&(mycontrol->cond)))<BR> return 1;<BR> mycontrol->active=0;<BR> return 0;<BR>}<BR>int control_activate(data_control *mycontrol) {<BR> int mystatus;<BR> if (pthread_mutex_lock(&(mycontrol->mutex)))<BR> return 0;<BR> mycontrol->active=1;<BR> pthread_mutex_unlock(&(mycontrol->mutex));<BR> pthread_cond_broadcast(&(mycontrol->cond));<BR> return 1;<BR>}<BR>int control_deactivate(data_control *mycontrol) {<BR> int mystatus;<BR> if (pthread_mutex_lock(&(mycontrol->mutex)))<BR> return 0;<BR> mycontrol->active=0;<BR> pthread_mutex_unlock(&(mycontrol->mutex));<BR> pthread_cond_broadcast(&(mycontrol->cond));<BR> return 1;<BR>}</PRE>
<H2><A name=10><SPAN class=atitle>调试时间</SPAN></A><BR></H2>
<P>在开始调试之前,还需要一个文件。以下是 dbug.h:<BR><BR><A
name=N10149><STRONG>dbug.h</STRONG></A></P><PRE class=displaycode>#define dabort() \<BR> { printf("Aborting at line %d in source file %s\n",__LINE__,__FILE__); abort(); }</PRE>
<P>此代码用于处理工作组代码中的不可纠正错误。</P>
<H2><A name=11><SPAN class=atitle>工作组代码</SPAN></A></H2><BR>
<P>说到工作组代码,以下就是:<BR><BR><A name=N1015F><STRONG>workcrew.c</STRONG></A></P><PRE class=displaycode>#include <stdio.h><BR>#include <stdlib.h><BR>#include "control.h"<BR>#include "queue.h"<BR>#include "dbug.h"<BR>/* the work_queue holds tasks for the various threads to complete. */<BR>struct work_queue {<BR> data_control control;<BR> queue work;<BR>} wq;<BR>/* I added a job number to the work node. Normally, t
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -