📄 posix 线程详解1.htm
字号:
函数调用放在 if() 语句中只是为了方便地检测失败的调用。让我们查看一下 pthread_create 参数。第一个参数
&mythread 是指向 mythread 的指针。第二个参数当前为
NULL,可用来定义线程的某些属性。由于缺省的线程属性是适用的,只需将该参数设为 NULL。</P>
<P>第三个参数是新线程启动时调用的函数名。本例中,函数名为 thread_function()。当 thread_function()
返回时,新线程将终止。本例中,线程函数没有实现大的功能。它仅将 "Thread says hi!" 输出 20 次然后退出。注意
thread_function() 接受 void * 作为参数,同时返回值的类型也是 void *。这表明可以用 void *
向新线程传递任意类型的数据,新线程完成时也可返回任意类型的数据。那如何向线程传递一个任意参数?很简单。只要利用
pthread_create() 中的第四个参数。本例中,因为没有必要将任何数据传给微不足道的
thread_function(),所以将第四个参数设为 NULL。</P>
<P>您也许已推测到,在 pthread_create() 成功返回之后,程序将包含两个线程。等一等, <B>两个</B>
线程?我们不是只创建了一个线程吗?不错,我们只创建了一个进程。但是主程序同样也是一个线程。可以这样理解:如果编写的程序根本没有使用
POSIX 线程,则该程序是单线程的(这个单线程称为“主”线程)。创建一个新线程之后程序总共就有两个线程了。 </P>
<P>我想此时您至少有两个重要问题。第一个问题,新线程创建之后主线程如何运行。答案,主线程按顺序继续执行下一行程序(本例中执行 "if
(pthread_join(...))")。第二个问题,新线程结束时如何处理。答案,新线程先停止,然后作为其清理过程的一部分,等待与另一个线程合并或“连接”。</P>
<P>现在,来看一下 pthread_join()。正如 pthread_create() 将一个线程拆分为两个,
pthread_join() 将两个线程合并为一个线程。pthread_join() 的第一个参数是 tid
mythread。第二个参数是指向 void 指针的指针。如果 void 指针不为 NULL,pthread_join 将线程的
void * 返回值放置在指定的位置上。由于我们不必理会 thread_function() 的返回值,所以将其设为 NULL.</P>
<P>您会注意到 thread_function() 花了 20 秒才完成。在 thread_function()
结束很久之前,主线程就已经调用了 pthread_join()。如果发生这种情况,主线程将中断(转向睡眠)然后等待
thread_function() 完成。当 thread_function() 完成后, pthread_join()
将返回。这时程序又只有一个主线程。当程序退出时,所有新线程已经使用 pthread_join()
合并了。这就是应该如何处理在程序中创建的每个新线程的过程。如果没有合并一个新线程,则它仍然对系统的最大线程数限制不利。这意味着如果未对线程做正确的清理,最终会导致
pthread_create() 调用失败。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="POSIX 线程详解1.files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="POSIX 线程详解1.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 线程详解1.files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="POSIX 线程详解1.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_thread1/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N100BB><SPAN class=atitle>无父,无子</SPAN></A></P>
<P>如果使用过 fork() 系统调用,可能熟悉父进程和子进程的概念。当用 fork()
创建另一个新进程时,新进程是子进程,原始进程是父进程。这创建了可能非常有用的层次关系,尤其是等待子进程终止时。例如,waitpid()
函数让当前进程等待所有子进程终止。waitpid() 用来在父进程中实现简单的清理过程。</P>
<P>而 POSIX 线程就更有意思。您可能已经注意到我一直有意避免使用“父线程”和“子线程”的说法。这是因为 POSIX
线程中不存在这种层次关系。虽然主线程可以创建一个新线程,新线程可以创建另一个新线程,POSIX
线程标准将它们视为等同的层次。所以等待子线程退出的概念在这里没有意义。POSIX
线程标准不记录任何“家族”信息。缺少家族信息有一个主要含意:如果要等待一个线程终止,就必须将线程的 tid 传递给
pthread_join()。线程库无法为您断定 tid。</P>
<P>对大多数开发者来说这不是个好消息,因为这会使有多个线程的程序复杂化。不过不要为此担忧。POSIX
线程标准提供了有效地管理多个线程所需要的所有工具。实际上,没有父/子关系这一事实却为在程序中使用线程开辟了更创造性的方法。例如,如果有一个线程称为线程
1,线程 1 创建了称为线程 2 的线程,则线程 1 自己没有必要调用 pthread_join() 来合并线程
2,程序中其它任一线程都可以做到。当编写大量使用线程的代码时,这就可能允许发生有趣的事情。例如,可以创建一个包含所有已停止线程的全局“死线程列表”,然后让一个专门的清理线程专等停止的线程加到列表中。这个清理线程调用
pthread_join() 将刚停止的线程与自己合并。现在,仅用一个线程就巧妙和有效地处理了全部清理。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt="" src="POSIX 线程详解1.files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="POSIX 线程详解1.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 线程详解1.files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="POSIX 线程详解1.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_thread1/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N100CA><SPAN class=atitle>同步漫游</SPAN></A></P>
<P>现在我们来看一些代码,这些代码做了一些意想不到的事情。thread2.c 的代码如下:</P><BR><A
name=N100D3><B>thread2.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;
void *thread_function(void *arg) {
int i,j;
for ( i=0; i<20; i++) {
j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
myglobal=j;
}
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++) {
myglobal=myglobal+1;
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 线程详解1.files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="POSIX 线程详解1.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 线程详解1.files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="POSIX 线程详解1.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_thread1/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N100DD><SPAN class=atitle>理解 thread2.c</SPAN></A></P>
<P>如同第一个程序,这个程序创建一个新线程。主线程和新线程都将全局变量 myglobal 加一 20
次。但是程序本身产生了某些意想不到的结果。编译代码请输入:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee
border=1>
<TBODY>
<TR>
<TD><PRE><CODE class=section>
$ gcc thread2.c -o thread2 -lpthread
</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>
$ ./thread2
</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>
$ ./thread2
..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
myglobal equals 21
</CODE></PRE></TD></TR></TBODY></TABLE><BR>
<P>非常意外吧!因为 myglobal 从零开始,主线程和新线程各自对其进行了 20 次加一, 程序结束时 myglobal
值应当等于 40。由于 myglobal 输出结果为 21,这其中肯定有问题。但是究竟是什么呢?</P>
<P>放弃吗?好,让我来解释是怎么一回事。首先查看函数 thread_function()。注意如何将 myglobal 复制到局部变量
"j" 了吗? 接着将 j 加一, 再睡眠一秒,然后到这时才将新的 j 值复制到
myglobal?这就是关键所在。设想一下,如果主线程就在新线程将 myglobal 值复制给 j <B>后</B> 立即将
myglobal 加一,会发生什么?当 thread_function() 将 j 的值写回 myglobal
时,就覆盖了主线程所做的修改。 </P>
<P>当编写线程程序时,应避免产生这种无用的副作用,否则只会浪费时间(当然,除了编写关于 POSIX
线程的文章时有用)。那么,如何才能排除这种问题呢?</P>
<P>由于是将 myglobal 复制给 j 并且等了一秒之后才写回时产生问题,可以尝试避免使用临时局部变量并直接将 myglobal
加一。虽然这种解决方案对这个特定例子适用,但它还是不正确。如果我们对 myglobal
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -