📄 posix 线程详解2.htm
字号:
<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=N100AF><SPAN class=atitle>线程内幕 1</SPAN></A></P>
<P>在解释如何确定在何处使用互斥对象之前,先来深入了解一下线程的内部工作机制。请看第一个例子:</P>
<P>假设主线程将创建三个新线程:线程 a、线程 b 和线程 c。假定首先创建线程 a,然后是线程 b,最后创建线程 c。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
pthread_create( &thread_a, NULL, thread_function, NULL);
pthread_create( &thread_b, NULL, thread_function, NULL);
pthread_create( &thread_c, NULL, thread_function, NULL);
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>在第一个 pthread_create() 调用完成后,可以假定线程 a 不是已存在就是已结束并停止。第二个
pthread_create() 调用后,主线程和线程 b 都可以假定线程 a 存在(或已停止)。</P>
<P>然而,就在第二个 create() 调用返回后,主线程无法假定是哪一个线程(a 或 b)会首先开始运行。虽然两个线程都已存在,线程
CPU 时间片的分配取决于内核和线程库。至于谁将首先运行,并没有严格的规则。尽管线程 a 更有可能在线程 b
之前开始执行,但这并无保证。对于多处理器系统,情况更是如此。如果编写的代码假定在线程 b 开始执行之前实际上执行线程 a
的代码,那么,程序最终正确运行的概率是 99%。或者更糟糕,程序在您的机器上 100%
地正确运行,而在您客户的四处理器服务器上正确运行的概率却是零。</P>
<P>从这个例子还可以得知,线程库保留了每个单独线程的代码执行顺序。换句话说,实际上那三个 pthread_create()
调用将按它们出现的顺序执行。从主线程上来看,所有代码都是依次执行的。有时,可以利用这一点来优化部分线程程序。例如,在上例中,线程 c
就可以假定线程 a 和线程 b 不是正在运行就是已经终止。它不必担心存在还没有创建线程 a 和线程 b
的可能性。可以使用这一逻辑来优化线程程序。</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=N100C8><SPAN class=atitle>线程内幕 2</SPAN></A></P>
<P>现在来看另一个假想的例子。假设有许多线程,他们都正在执行下列代码:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
myglobal=myglobal+1;
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>那么,是否需要在加一操作语句前后分别锁定和解锁互斥对象呢?也许有人会说“不”。编译器极有可能把上述赋值语句编译成一条机器指令。大家都知道,不可能"半途"中断一条机器指令。即使是硬件中断也不会破坏机器指令的完整性。基于以上考虑,很可能倾向于完全省略
pthread_mutex_lock() 和 pthread_mutex_unlock() 调用。不要这样做。</P>
<P>我在说废话吗?不完全是这样。首先,不应该假定上述赋值语句一定会被编译成一条机器指令,除非亲自验证了机器代码。即使插入某些内嵌汇编语句以确保加一操作的完整执行――甚至,即使是自己动手写编译器!--
仍然可能有问题。</P>
<P>答案在这里。使用单条内嵌汇编操作码在单处理器系统上可能不会有什么问题。每个加一操作都将完整地进行,并且多半会得到期望的结果。但是多处理器系统则截然不同。在多
CPU 机器上,两个单独的处理器可能会在几乎同一时刻(或者,就在同一时刻)执行上述赋值语句。不要忘了,这时对内存的修改需要先从 L1
写入 L2 高速缓存、然后才写入主存。(SMP 机器并不只是增加了处理器而已;它还有用来仲裁对 RAM
存取的特殊硬件。)最终,根本无法搞清在写入主存的竞争中,哪个 CPU
将会"胜出"。要产生可预测的代码,应使用互斥对象。互斥对象将插入一道"内存关卡",由它来确保对主存的写入按照线程锁定互斥对象的顺序进行。</P>
<P>考虑一种以 32 位块为单位更新主存的 SMP 体系结构。如果未使用互斥对象就对一个 64 位整数进行加一操作,整数的最高 4
位字节可能来自一个 CPU,而其它 4 个字节却来自另一
CPU。糟糕吧!最糟糕的是,使用差劲的技术,您的程序在重要客户的系统上有可能不是很长时间才崩溃一次,就是早上三点钟就崩溃。David
R. Butenhof 在他的《POSIX 线程编程》(请参阅本文末尾的 <A
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread2/#resources">参考资料</A>部分)一书中,讨论了由于未使用互斥对象而将产生的种种情况。
</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=N100E5><SPAN class=atitle>许多互斥对象</SPAN></A></P>
<P>如果放置了过多的互斥对象,代码就没有什么并发性可言,运行起来也比单线程解决方案慢。如果放置了过少的互斥对象,代码将出现奇怪和令人尴尬的错误。幸运的是,有一个中间立场。首先,互斥对象是用于串行化存取*共享数据*。不要对非共享数据使用互斥对象,并且,如果程序逻辑确保任何时候都只有一个线程能存取特定数据结构,那么也不要使用互斥对象。</P>
<P>其次,如果要使用共享数据,那么在读、写共享数据时都应使用互斥对象。用 pthread_mutex_lock() 和
pthread_mutex_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=N100F1><SPAN class=atitle>使用调用:初始化</SPAN></A></P>
<P>现在该来看看使用互斥对象的各种不同方法了。让我们从初始化开始。在 <A
href="http://www-128.ibm.com/developerworks/cn/linux/thread/posix_thread2/#thread3.c">thread3.c
示例</A> 中,我们使用了静态初始化方法。这需要声明一个 pthread_mutex_t 变量,并赋给它常数
PTHREAD_MUTEX_INITIALIZER: </P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>很简单吧。但是还可以动态地创建互斥对象。当代码使用 malloc()
分配一个新的互斥对象时,使用这种动态方法。此时,静态初始化方法是行不通的,并且应当使用例程
pthread_mutex_init():</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
int pthread_mutex_init( pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr)
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>正如所示,pthread_mutex_init
接受一个指针作为参数以初始化为互斥对象,该指针指向一块已分配好的内存区。第二个参数,可以接受一个可选的
pthread_mutexattr_t 指针。这个结构可用来设置各种互斥对象属性。但是通常并不需要这些属性,所以正常做法是指定
NULL。</P>
<P>一旦使用 pthread_mutex_init() 初始化了互斥对象,就应使用 pthread_mutex_destroy()
消除它。pthread_mutex_destroy() 接受一个指向 pthread_mutext_t
的指针作为参数,并释放创建互斥对象时分配给它的任何资源。请注意, pthread_mutex_destroy() <B>不会</B>
释放用来存储 pthread_mutex_t
的内存。释放自己的内存完全取决于您。还必须注意一点,pthread_mutex_init() 和
pthread_mutex_destroy() 成功时都返回零。 </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=N10112><SPAN class=atitle>使用调用:锁定</SPAN></A></P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
pthread_mutex_lock(pthread_mutex_t *mutex)
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>pthread_mutex_lock()
接受一个指向互斥对象的指针作为参数以将其锁定。如果碰巧已经锁定了互斥对象,调用者将进入睡眠状态。函数返回时,将唤醒调用者(显然)并且调用者还将保留该锁。函数调用成功时返回零,失败时返回非零的错误代码。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
pthread_mutex_unlock(pthread_mutex_t *mutex)
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -