线程是 CPU 调度的最小执行单位,你可以创建一个线程用于 ListenMusic,再创建一个线程去 PlayGame,这样操作系统就是同时处理这两个任务的(并发)。
POSIX 标准定义了一套线程操作相关的函数,用于让程序员更加方便地操作管理线程,函数名都是以前缀 pthread_ 开始,使用时要包含
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
功能说明:创建一个线程。
参数说明
thread:线程句柄,需要先定义一个 pthread_t 类型变量 thread,将该变量的地址 &thread 传递到该参数中去。这是一个传出参数,传递进去的 thread 会得到系统为我们创建好的线程句柄。
attr:线程属性,通过该参数可以设置创建的线程属性,如果要使用默认属性直接传递 NULL 即可。
start_routine:线程函数,它是一个函数指针类型,返回类型为 void *,参数为一个 void * 类型变量,创建好这样类型的一个函数,将函数名传递进去即可。
arg:线程参数,代表需要在主线程传递给子线程的参数,给 arg 赋值后可以在线程函数的参数中取到。
返回值说明
成功情况下返回 0,失败情况下返回错误码,并且 tid 的值是不确定的。Linux 环境下所有线程函数调用失败时均是返回错误码,除了部分返回值为 void 的函数。关于错误码的说明在这里的第 8 小节。
函数原型:pthread_t pthread_self(void);
功能说明:获取线程 ID。
参数说明:无参数。
返回值说明:如果在主线程中调用该函数会返回主线程的线程 ID,如果在子线程中调用该函数会返回子线程的线程 ID,该函数没有失败的情况。
额外说明:线程 ID 是进程内部识别标志,两个进程间线程 ID 允许相同。
函数原型:int pthread_equal(pthread_t t1, pthread_t t2);
功能说明:比较两个线程 ID 是否相等,在 Linux 系统中 pthread_t 都设计为 unsigned long 类型,所以可以直接用 == 判别是否相等,但是如果某些系统设计为结构体类型,那么就可以通过 pthread_equal 函数判别是否相等了。
参数说明:要比较的两个线程 ID。
返回值说明:若相等返回非 0 数值,否则返回 0。
函数原型:void pthread_exit(void *retval);
功能说明:将单个线程退出。
参数说明:retval 为该线程的返回状态,如果主线程调用 pthread_join 可以获取到该返回状态。
返回值说明:void。
额外说明:如果在主线程中调用了 pthread_exit(NULL),则主线程退出,如果子线程存在会继续执行。如下代码,就算主线程先退出了,也不会影响子线程打印"我是子线程"。注意如果使用 exit 退出会导致整个进程结束。
函数原型:int pthread_join(pthread_t thread,void **retval);
功能说明:阻塞等待线程退出,获取线程退出状态,相当于进程中的 waitpid 函数,如果线程退出,pthread_join 立刻返回。
参数说明
thread:代表要等待线程的线程 ID
retval:获取该线程的退出状态
返回值说明
成功情况下返回 0,失败则返回错误码。
函数原型:int pthread_detach(pthread_t thread);
功能说明:将线程 ID 为 thread 的线程分离出去,所谓分离出去就是指主线程不需要再通过 pthread_join 等方式等待该线程的结束并回收其线程控制块(TCB)的资源,被分离的线程结束后由操作系统负责其资源的回收。
参数说明:thread 为要分离的线程的线程 ID。
返回值说明
成功情况下返回 0,失败情况下返回错误码。
额外说明
一般来说,主线程是要负责创建出来的子线程的资源回收工作的。如果主线程先于子线程退出并且子线程没有设置为分离状态,那么子线程结束后其资源是无法得到回收的,会造成资源浪费和系统臃肿;如果主线程先于子线程退出但是子线程时分离状态,那么子线程退出的时候操作系统会自动回收其资源。
函数原型:int pthread_cancel(pthread_t thread);
功能说明:杀死线程。
参数说明:thread 为要杀死的线程的线程 ID。
返回值说明:成功情况下返回 0,失败情况下返回错误码。
程序说明
分别创建了两个线程,pthread_func3 是一个死循环。在主线程中 sleep(3) 之后调用 pthread_cancel 结束子线程 pthread_func3,再调用 pthread_join 回收该子线程,由于子线程已经被杀死,此时 pthread_join 返回 -1。
如果在子线程中的 while(1) 里,将 printf 和 sleep 函数注释掉,会发现杀不死该线程,原因是 pthread_cancel 并不等待线程终止,它仅仅提出了一种请求,需要子线程执行到特定的取消点才能终止该线程(如一些系统调用 write,pause 等地方)。如果线程中没有系统调用的函数,可以加入 pthread_testcancel 函数作为取消点。
EDEADLK:检测到线程发生死锁
EINVAL:线程不是可以等待回收的线程(后续会讲到线程分离)或者有其他线程已经准备等待回收该线程了
ESRCH:该线程 ID 不存在
在 Linux 编程中,多线程编程无疑是十分重要的。它可以提高程序的并发处理能力,线程间通信和共享数据十分方便,而且相对于多进程来说,它更加轻量级、好用。