4、线程创建
传统的 unix
进程模型,只支持每个进程只有一个线程控制。在概念上来说,这和基于线程模型的只有一个线程的进程是一样的。使用 pthreads
,当一个程序运行的时候,它会启动一个只有一个线程的进程,程序运行的时候,如果它不创建新的线程,那么它和传统的 unix
进程运行没有什么两样.通过 pthread_create
可以创建线程。
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
void *(*start_rtn)(void), void *restrict arg);
返回0表示成功,失败则返回错误号码。
当 pthread_create
函数返回成功的时候, tidp
指向新创建的线程的 id
的内存地址; attr
用来自定义各种线程属性,后面会讲到,这里设置为 NULL
表示采用默认的属性.
新创建的线程从 start_rtn
函数指针指向的地址开始运行, arg
是传递给这个函数的参数,它是一个无类型的指针,如果想要给函数传递多个参数那么就将参数存放在一个结构体中,把结构体的地址赋给 arg
.
当一个线程创建的时候,无法确保是调用线程先运行还是新创建的线程先运行。新创建的线程可以访问进程空间地址,继承调用线程的 floating-point
环境和 signal mask
,然而被 pending
的信号会被清除。
注意,线程函数失败的时候会返回一个错误码。它不象其他会设置 errno
变量,为每个线程提供错误码,只是为了兼容使用它们的函数.对于线程来说,从函数中返回错误码是很清晰的做法,这样把错误的范围就只限定在产生这个错误的函数的身上了,而不是通过修改一个全局性质的变量,使得这个函数具有一些副作用。
举例:尽管没有一个打印线程ID的可移植的方法,我们可以自己写一个小的测试程序来实现它,这样可以看到一些线程是如何工作的信息。后面的程序就是创建了一个线程,然后打印进程 ID
,主线程 ID
,以及新创建的线程 ID
.
为了处理主线程和新线程之间的竞争,这个例子有两个比较奇怪的行为:
1)主线程需要睡眠一会。如果主线程不睡眠,那么可能在新创建的线程还没有来得及运行的时候主线程就结束了,进而导致整个进程的退出。这个取决于系统的线程功能实现以及调度算法。
2)新线程是通过pthread_self来获取自己的线程id。新线程不是通过读取共享的内存或者pthread_create的参数(tidp)来获得它的线程id的,因为这样不安全。如果这样使用,那么新创建的线程若先运行的话,那么调用线程还没有来得及初始化这些数据,就会被新线程使用了。
对于这个例子,具体的代码参见参考资料,我们看到的现象是:
Solaris中,两个线程的进程id相等,线程id是两个整数。主线程比新线程先运行。
FreeBSD中,两个线程的进程id相等,线程id是两个相差范围不大的地址。主线程比新线程先运行。
MacOS X中,两个线程的进程id相等,线程id是两个相差范围很大的地址。主线程比新线程先运行。
Linux中,两个线程的进程id不相等,线程id是两个整数。新线程比主线程先运行。
Linux中两个线程的进程id不相等,这是个不足的地方,它是使用特殊参数的clone系统调用来创建子进程,子进程可以通过参数配置共享父进程哪些上下文环境,例如文件描述符号或者内存。
注意这个例子的现象中我们可以看到,除了linux之外,其他的系统都是主线程先运行。这样我们可以知道我们不能随意假设主线程或者新线程究竟哪个首先被运行。
网友评论