
线程创建
#include
参数解析: tidp:当pthread_create成功返回后,新创建线程的线程ID会被设置成tidp指向的内存单元。
attr:用于定制不同的线程属性(在第12章会详细讨论),将其设置为NULL将会拥有默认的属性
start_rtn:新创建的线程开始的地址,其实就是兄弟线程。这里其实暴露了最好的结构,因为这是一个返回值为任意类型,参数为任意类型的指针函数。因此非常开放,可以是任何类型。
arg:结构体的地址
注意:
pthread函数在调用失败后通常会返回错误码。在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局状态,这样可以把错误的范围限制在引起出错的函数中。
举例(书中)
#include
输出:

解释: 在这里,main进程先是创建了一个新的线程,其中线程id是ntid属性默认,兄弟线程设置为thr_fn,其余参数设置为NULL。 最终输出创建出来的线程的各种参数和main线程的各种参数。
举例2
#include
解析:
在这里,可以看到首先输出Begin,然后创建一个线程,最后输出Hello world。 创建线程的时候会执行兄弟线程。
注意: 因为兄弟线程和main线程的执行线程是不确定的,因此如下图所示,兄弟线程的语句什么时候输出以及是否输出都是不确定的。

线程的终止
3种终止线程的方式
- 线程可以简单地从启动例程中返回,返回值是线程的退出码
- 线程可以被同一进程中的其他线程取消
- 线程调用pthread_exit
#include
参数: rval_ptr参数是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程也可以通过调用pthread_join函数访问到这个指针。
#include
参数: 其中pthread_exit的参数和pthread_join函数的第二个参数是相同的,pthread_join函数的含义是将调用点成阻塞,直到指定的线程(也就是thread参数指定的)从它的启动例程返回,rval_ptr包含返回码。如果线程被取消,由rval_ptr指定的内存单元就被设置为PTHREAD_CANCELED。
pthread_create和pthread_join线程举例
#include
输出:

解释: main线程首先创建了一个子线程,线程号被指向tid,属性是默认属性,兄弟线程是func。创建线程之后用pthread_join来等待这个线程退出,最后执行End!
注意:若没有pthread_join则输出顺序是不确定的。就和例1一样。
我的例子
#include
可以看到,我们首先创建了一个pthread_t类型的变量tid,初始值是-407197560.然后打印Begin,调用pthread_create来创建一个线程,指向tid,属性为NULL,兄弟线程是func,可以看到,线程号在调用期间被初始化成了141393920.然后结束之后打印End。
例11.3(获取已终止线程的退出码)
#include
输出:

不要传递临时变量
pthread_create和pthread_exit函数的无类型指针参数可以传递的值可以是结构体等一系列类型,但是这个结构所使用的内存在调用之后必须仍然是有效的。也就是说,不要是临时变量(存放在栈上),需要是存放在堆上的常量或者其他数据。参考指针函数和函数指针。里面有详细的介绍。
用自动变量(分配在栈上)作为pthread_exit的参数时出现的问题。
#include
输出:

注意:这是macos上的输出,每个操作系统的输出不一定相同。 在这里,在第41行调用tid1线程时将fp进行了初始化,但是调用结束之后内存就回收了,因此被覆盖了。在第50行再次被调用时已经没有办法显示原来的值了。
线程的取消
函数:pthread_cancel();
#include
在默认情况下,pthread_cancel函数会使得tid表示的线程的行为表现为如同调用了PTHREAD_CANCELED和pthread_exit函数,但是,线程可以选择忽略取消或者控制如何被取消。 注意:pthread_cancel并不等于线程终止,它仅仅提出请求。
取消两种状态
1.允许
允许取消也分为两种:
① 异步cancel,推迟cancel(默认)->推迟至cancel点在响应,也就是说不到cancel点是不会直接取消的。 cancel点:就是允许cancel的位置
② 同步cancel
2.不允许
线程清理处理程序
线程可以安排它推出时需要调用的函数,这与进程在退出时可以用atexit函数安排退出是类似的。这样的函数称为线程清理处理程序。
一个线程可以建立多个线程处理程序,处理程序记录在栈中,也就是说,它们的执行顺序与它们注册时相反。
#include
举例
#include
输出:

可以看到,退出时会依次执行三个退出时我们所需要调用的函数。并且执行顺序与注册时相反。
注意
来看两种特殊情况, 1. 我们将弹出栈pop的三行设置如下:
pthread_cleanup_pop
此时会只有一个输出,如下:

可以看到只有最后一次push的值输出的。其余的都没有输出
2. 若我们将上面的三行放到pthread_exit(NULL)之后,那么理论上是看不到的,但是因为线程没办法看到,所以pop值全部默认为1!! !

此时的输出为:

并且即使看不见,也必须在程序里体现,因为在宏定义中push里有一个{,而}在pop中,因此必须得成对出现,数量相同。
cancel点
在POSIX标准中,只有在cancel点才会执行阻塞系统调用