在传统的UNIX进程模型中,每个进程只有一个控制线程。从概念上讲,这与基于线程的模型中只包含一个线程是相同的。在POSIX线程(pthread)的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的,在创建多个控制线程以前,程序的行为与传统的进程并没有什么区别。新增的线程可以通过调用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,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。
注意pthread函数在调用失败时通常会返回错误码,它们并不像其他的POSIX函数一样设置errno。每个线程都提供errno的副本,这只是为了与使用errno的现有函数兼容。在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局状态,因而可以把错误的范围限制在引起出错的函数中。
实例
虽然没有可移植的方法打印线程ID,但是可以写一个小的测试程序来完成这个任务,以便更深入地了解线程是如何工作的。程序清单11-1中创建了一个线程并且打印进程ID、新线程的线程ID以及初始线程的线程ID。
程序清单11-1 打印线程ID
#include "apue.h" #include <pthread.h> pthread_t ntid; void printids(const char *s) { pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); printf("%s pid %u tid %u (0x%x) ", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid); } void * thr_fn(void *arg) { printids("new thread : "); return((void *)0); } int main(void) { int err; err = pthread_create(&ntid, NULL, thr_fn, NULL); if(err != 0) err_quit("can't create thread: %s ", strerror(err)); printids("main thread: "); sleep(1); exit(0); }
编译上面程序过程中遇到了undefined reference to ‘pthread_create’的问题:
首先,检查了函数名,没有写错;然后检查了头文件<pthread.h>,也包含了。这种摸不着头脑的问题总是让人很纠结的......最终在网上找到了出现该问题的原因及其解决方案,参考自:http://blog.csdn.net/llqkk/article/details/2854558
问题原因:
pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
解决方案:
在编译中要加 -lpthread参数
问题解决了就OK了吗?当然不行,由此问题恰好暴露了之前学过的一些知识其实并没有真正吃透,学的时候理解,遇到问题的时候却想不起来。其实上面出现的这个问题牵涉到了之前学过的如下两个知识点:
(1)软件开发工具GCC http://www.cnblogs.com/nufangrensheng/p/3426405.html。注意其中的GCC链接选项部分。
(2)UNIX标准化及实现之POSIX标准可选头文件 http://www.cnblogs.com/nufangrensheng/p/3496116.html。从中可以看到pthread.h是POSIX标准的可选头文件,也就是说pthread库不是Linux系统默认的库,链接时需要使用静态库libpthread.a。
更多关于undefined reference to...的问题可参考:http://ticktick.blog.51cto.com/823160/431329
现在我们言归正传,从程序清单11-1运行结果来看,两个线程的进程ID相同,但线程ID不同。不过这不是绝对的,依赖于具体的实现。
这个实例有两个需要注意的地方:
(1)需要处理主线程和新线程之间的竞争。首先是主线程需要休眠,如果主线程不休眠,它就可能退出,这样在新线程有机会运行之前整个进程可能就已经终止了。这种行为特征依赖于操作系统中的线程实现和调度算法。
(2)新线程是通过调用pthread_self函数获取自己的线程ID,而不是从共享内存中读出或者从线程的启动例程中以参数的形式接收到。回忆pthread_create函数,它会通过第一个参数(tidp)返回新建线程的线程ID。在本例中,主线程把新线程ID存放在ntid中,但是新建的线程并不能安全地使用它,如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程看到的是未经初始化的ntid的内容,这个内容并不是正确的线程ID。
本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/。