1. 线程概念
每个线程都包含有表示执行环境所必须的信息(即每个线程都有属于自己的以下信息,不同线程之间不共享不一致):
线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。
一个进程的所有信息对该进程的所有线程都是共享的:
可执行程序的代码、程序的全局内存和堆内存、栈内存以及文件描述符
2. 线程标识
每个线程都有一个线程ID。与进程ID不同,进程ID在整个系统中是唯一的,但是线程ID只有在它所属的进程上下文中才有意义。
线程ID通过pthread_t数据类型表示。对于该类型的实现不同操作系统不同,Linux是unsigned long int
,某些操作系统用一个结构体实现pthread_t。因此不能将pthread_t类型当做整数处理(如用数值的方式进行比较)。因此必须通过一个函数来对两个线程ID进行比较
通过pthread_equal比较两个线程ID
int pthread_equal(pthread_t t1, pthread_t t2);
通过pthread_self函数获得自身线程ID
pthread_t pthread_self(void);
3. 线程创建
通过pthread_create函数创建一个线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- thread参数:若线程创建成功,保存该线程ID
- attr:定制各种不同的线程属性。NULL即为默认属性
- start_routine:线程从该地址开始运行,该函数接受一个void *参数并返回一个void *类型
- arg:传递给start_routine的参数值。如果需要向start_routine函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
注意,创建线程时不能保证哪个线程会先执行。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
注意,对于pthread_create函数的第一个参数,不能在start_routine线程函数中使用!因为无法保证哪个线程先执行,因此如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程看到的是未经初始化的thread成员。应该通过pthread_self获取自身线程ID。
4. 线程终止
如果进程中的任何线程调用了exit、_Exit或_exit,那么整个进程就会终止。
对于某个信号,如果默认的动作是终止进程,那么发送到线程的该信号就会终止整个进程。
单个线程可以通过以下3种方式退出,并且不导致整个进程终止:
- 线程简单地从启动例程中返回,返回值是线程的退出码
- 线程可以被同一进程中的其他线程取消
- 线程调用pthread_exit
4.1 pthread_exit
void pthread_exit(void *retval);
retval是一个void *类型指针,即为线程退出码,进程中的其他线程可以通过pthread_join函数访问这个指针。
4.2 pthread_join
int pthread_join(pthread_t thread, void **retval);
调用线程一直阻塞,直到指定线程退出(调用pthread_exit、从启动例程中范湖或被取消)。retval保存了指定线程return值、pthread_exit参数值、如果线程是被pthread_cancel的则是PTHREAD_CANCELED。
如果线程处于分离状态(pthread_detach),则pthread_join失败,返回EINVAL。
示例:
void * func1(void* arg) {
cout << "线程1运行" << endl;
return (void*)1;
}
void * func2(void* arg) {
cout << "线程2运行" << endl;
pthread_exit((void*)2)