📄 posix 线程详解2.htm
字号:
href="http://www-128.ibm.com/developerworks/cn/kickstart/">Java
应用开发源动力 - 下载免费软件,快速启动开发</A>
</P></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!--END RESERVED FOR FUTURE USE INCLUDE FILES--><BR></TD></TR></TBODY></TABLE>
<P>级别: 初级</P>
<P><A
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread2/#author">Daniel
Robbins</A>, 总裁/CEO, Gentoo Technologies, Inc.<BR></P>
<P>2000 年 8 月 01 日</P>
<BLOCKQUOTE>POSIX 线程是提高代码响应和性能的有力手段。在此三部分系列文章的第二篇中,Daniel Robbins
将说明,如何使用被称为互斥对象的灵巧小玩意,来保护线程代码中共享数据结构的完整性。</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-->
<P><A name=N10045><SPAN class=atitle>互斥我吧!</SPAN></A></P>
<P>在 <A
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.html">前一篇文章中</A>
,谈到了会导致异常结果的线程代码。两个线程分别对同一个全局变量进行了二十次加一。变量的值最后应该是 40,但最终值却是
21。这是怎么回事呢?因为一个线程不停地“取消”了另一个线程执行的加一操作,所以产生这个问题。现在让我们来查看改正后的代码,它使用
<B>互斥对象</B>(mutex)来解决该问题: </P><BR><A
name=thread3.c><B>thread3.c</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void *arg) {
int i,j;
for ( i=0; i<20; i++) {
pthread_mutex_lock(&mymutex);
j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
myglobal=j;
pthread_mutex_unlock(&mymutex);
}
return NULL;
}
int main(void) {
pthread_t mythread;
int i;
if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
printf("error creating thread.");
abort();
}
for ( i=0; i<20; i++) {
pthread_mutex_lock(&mymutex);
myglobal=myglobal+1;
pthread_mutex_unlock(&mymutex);
printf("o");
fflush(stdout);
sleep(1);
}
if ( pthread_join ( mythread, NULL ) ) {
printf("error joining thread.");
abort();
}
printf("\nmyglobal equals %d\n",myglobal);
exit(0);
}
</CODE></PRE></TD></TR></TBODY></TABLE><BR><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="POSIX 线程详解2.files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="POSIX 线程详解2.files/c.gif" width=8
border=0></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD><IMG height=4 alt="" src="POSIX 线程详解2.files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="POSIX 线程详解2.files/u_bold.gif" width=16
border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread2/#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N1005F><SPAN class=atitle>解读一下</SPAN></A></P>
<P>如果将这段代码与 <A
href="http://www.ibm.com/software/developerworks/library/posix1.html">前一篇文章</A>
中给出的版本作一个比较,就会注意到增加了 pthread_mutex_lock() 和 pthread_mutex_unlock()
函数调用。在线程程序中这些调用执行了不可或缺的功能。他们提供了一种
<I>相互排斥</I>的方法(互斥对象即由此得名)。两个线程不能同时对同一个互斥对象加锁。 </P>
<P>互斥对象是这样工作的。如果线程 a 试图锁定一个互斥对象,而此时线程 b 已锁定了同一个互斥对象时,线程 a
就将进入睡眠状态。一旦线程 b 释放了互斥对象(通过 pthread_mutex_unlock() 调用),线程 a
就能够锁定这个互斥对象(换句话说,线程 a 就将从 pthread_mutex_lock()
函数调用中返回,同时互斥对象被锁定)。同样地,当线程 a 正锁定互斥对象时,如果线程 c 试图锁定互斥对象的话,线程 c
也将临时进入睡眠状态。对已锁定的互斥对象上调用 pthread_mutex_lock()
的所有线程都将进入睡眠状态,这些睡眠的线程将“排队”访问这个互斥对象。</P>
<P>通常使用 pthread_mutex_lock() 和 pthread_mutex_unlock()
来保护数据结构。这就是说,通过线程的锁定和解锁,对于某一数据结构,确保某一时刻只能有一个线程能够访问它。可以推测到,当线程试图锁定一个未加锁的互斥对象时,POSIX
线程库将同意锁定,而不会使线程进入睡眠状态。</P><BR><A
name=figure1><B>请看这幅轻松的漫画,四个小精灵重现了最近一次 pthread_mutex_lock()
调用的一个场面。</B></A><BR><IMG height=280 alt=""
src="POSIX 线程详解2.files/mutex.gif" width=600 border=0 valign="top">
<BR>
<P>图中,锁定了互斥对象的线程能够存取复杂的数据结构,而不必担心同时会有其它线程干扰。那个数据结构实际上是“冻结”了,直到互斥对象被解锁为止。pthread_mutex_lock()
和 pthread_mutex_unlock()
函数调用,如同“在施工中”标志一样,将正在修改和读取的某一特定共享数据包围起来。这两个函数调用的作用就是警告其它线程,要它们继续睡眠并等待轮到它们对互斥对象加锁。当然,除非在
<I>每个</I> 对特定数据结构进行读写操作的语句前后,都分别放上 pthread_mutex_lock() 和
pthread_mutext_unlock() 调用,才会出现这种情况。 </P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="POSIX 线程详解2.files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="POSIX 线程详解2.files/c.gif" width=8
border=0></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD><IMG height=4 alt="" src="POSIX 线程详解2.files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="POSIX 线程详解2.files/u_bold.gif" width=16
border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread2/#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N1008E><SPAN class=atitle>为什么要用互斥对象?</SPAN></A></P>
<P>听上去很有趣,但究竟为什么要让线程睡眠呢?要知道,线程的主要优点不就是其具有独立工作、更多的时候是同时工作的能力吗?是的,确实是这样。然而,每个重要的线程程序都需要使用某些互斥对象。让我们再看一下示例程序以便理解原因所在。</P>
<P>请看 thread_function(),循环中一开始就锁定了互斥对象,最后才将它解锁。在这个示例程序中,mymutex 用来保护
myglobal 的值。仔细查看 thread_function(),加一代码把 myglobal
复制到一个局部变量,对局部变量加一,睡眠一秒钟,在这之后才把局部变量的值传回给 myglobal。不使用互斥对象时,即使主线程在
thread_function() 线程睡眠一秒钟期间内对 myglobal 加一,thread_function()
苏醒后也会覆盖主线程所加的值。使用互斥对象能够保证这种情形不会发生。(您也许会想到,我增加了一秒钟延迟以触发不正确的结果。把局部变量的值赋给
myglobal 之前,实际上没有什么真正理由要求 thread_function()
睡眠一秒钟。)使用互斥对象的新程序产生了期望的结果:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
$ ./thread3
o..o..o.o..o..o.o.o.o.o..o..o..o.ooooooo
myglobal equals 40
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>为了进一步探索这个极为重要的概念,让我们看一看程序中进行加一操作的代码:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
thread_function() 加一代码:
j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
myglobal=j;
主线程加一代码:
myglobal=myglobal+1;
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>如果代码是位于单线程程序中,可以预期 thread_function()
代码将完整执行。接下来才会执行主线程代码(或者是以相反的顺序执行)。在不使用互斥对象的线程程序中,代码可能(几乎是,由于调用了
sleep() 的缘故)以如下的顺序执行:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
thread_function() 线程 主线程
j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1); myglobal=myglobal+1;
myglobal=j;
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>当代码以此特定顺序执行时,将覆盖主线程对 myglobal
的修改。程序结束后,就将得到不正确的值。如果是在操纵指针的话,就可能产生段错误。注意到 thread_function()
线程按顺序执行了它的所有指令。看来不象是 thread_function()
有什么次序颠倒。问题是,同一时间内,另一个线程对同一数据结构进行了另一个修改。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -