# 线程控制原语
线程函数的程序在 `pthread` 库中,故链接时要加上参数 。 <font color="#FF0000">`-lpthread` </font> 选项
在线程中检查错误的方法应为
```c
fprintf(stderr, "xxx error: %s\n", strerror(ret));
```
## 查看线程ID
**作用:** 查看当前线程ID
**函数原型:** pthread_t pthread_self(void);
**返回值:** 线程ID
线程 ID:pthread_t 类型,在 Linux 下为无符号整数数`%lu`,在其他系统中可能是结构体实现
线程 ID 是 <font color="#FF0000">进程内部的识别标志</font>,两个进程间,线程 ID 允许相同。
## 线程创建
**函数原型:**
```c
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
```
**参数:**
- thread:传出参数,表示线程标识符的地址
- attr:线程属性结构体地址,通常为 NULL ,代表使用默认属性
- start_routine:子线程回调函数的入口地址,创建成功,在 `pthread_craete`函数返回时,该函数会被自动调用
- arg:传给线程函数的参数,没有就传 NULL
**返回值:**
- 成功:返回 0
- 失败:直接返回 `errno`
`pthread_create() `创建的线程从指定的回调函数开始运行,该函数运行完后,该线程也就退出了。线程依赖进程存在的,共享进程的资源,如果创建线程的进程结束了,线程也就结束了。
### demo
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
void *tun(void *arg){
printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]){
pthread_t tid;
int ret = pthread_create(&tid, NULL, tun, NULL); //创建子线程,使其执行 tuc 函数
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
printf("this is main pid is %d pid is %lu\n", getpid(), pthread_self());
sleep(1); //防止主线程执行结束释放了进程
return 0;
}
```
循环创建多个线程
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
void *tun(void *arg){
int i = (int)(long)arg; //与下面同理
sleep(i); //保证输出的顺序
printf("I'm %dth this is thread pid is %d tid is %lu\n",i+1, getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]){
pthread_t tid;
int i = 0;
for(i = 0;i < 5;i++){
int ret = pthread_create(&tid, NULL, tun, (void *)(long)i);//这里传值时由于指针为 8字节,所以先将 i 转化为 8字节数据类型,然后再转为 (void*)类型。
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
}
sleep(6);
printf("this is main pid is %d pid is %lu\n", getpid(), pthread_self());
return 0;
}
```
注意:在 `pthread_create` 中第四个参数在传一个变量时,此处不能传 `&i`,因为 i 是变化的当在子线程的函数中去访问 `i` 的地址时,`i`的值已经发生了改变。
## 线程退出
在进程中我们可以调用 `exit()` 函数或 `_exit()` 函数来结束进程,在一个线程中我们可以通过 `pthread_exit()` 在不终止整个进程的情况下停止它的控制流。
**作用:**:退出调用线程。=,一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放
**函数原型:** void pthread_exit(void *retval);
**参数:**
- retval:存储线程退出状态的指针,无退出值时,NULL。
#### demo
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
void *fun(){
return NULL;
}
void *tun(void *arg){
int i = (int)(long)arg;
sleep(i);
if(i == 2){
//exit(0);
//fun();
pthread_exit(NULL);
}
printf("I'm %dth this is thread pid is %d tid is %lu\n",i+1, getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]){
pthread_t tid;
int i = 0;
for(i = 0;i < 5;i++){
int ret = pthread_create(&tid, NULL, tun, (void *)(long)i);
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
}
sleep(6);
printf("this is main pid is %d pid is %lu\n", getpid(), pthread_self());
return 0;
}
```
**总结:**
- return :返回到调用者那里去
- exit:退出当前进程
- pthread_exit: 退出当前线程
## 线程回收
**作用:** 等待线程结束(此函数会阻塞),并回收线程资源与退出状态,类似进程的 `wait()` 函数。如果线程已经结束,那么该函数会立即返回。
**函数原型:** int pthread_join(pthread_t thread, void **retval);
**参数:**
- thread:要回收的线程号
- retval:二级指针,用来存储线程退出状态的指针的地址。
**返回值:**
- 成功:返回 0
- 失败:返回 erron
### demo
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
struct stu{
char name[256];
int var;
};
void *tun(void *arg){
struct stu *ttr;
ttr = malloc(sizeof(ttr));
ttr->var = 200;
strcpy(ttr->name, "I am lihua\n");
return (void *)ttr;
}
int main(int argc, char *argv[]){
pthread_t tid;
int ret = pthread_create(&tid, NULL, tun, NULL);
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
struct stu *sret;
// 等待线程号为 tid 的线程,如果此线程结束就回收其资源
// &sret保存线程退出的返回值
ret = pthread_join(tid, (void **)&sret);
if(ret != 0){
fprintf(stderr, "pthread_join error %s\n", strerror(ret));
}
printf("%s------%d\n", sret->name, sret->var);
return 0;
}
```
循环创建多个线程,并且回收
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
void *tun(void *arg){
int i = (int)(long)arg;
sleep(i);
printf("I'm %dth thread tid is %lu\n",i+1, pthread_self());
return (void *)pthread_self();
}
int main(int argc, char *argv[]){
pthread_t tid[5];
int i = 0;
for(i = 0;i < 5;i++){
int ret = pthread_create(&tid[i], NULL, tun, (void *)(long)i); //使用数组存储线程的地址
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
}
sleep(i);
for(i = 0;i < 5;i++){
int *tret;
pthread_join(tid[i], (void **)&tret);
printf("----pthread_join %dth thread id is %lu\n", i+1, (long)tret);
}
return 0;
}
```
## 杀死线程
**作用:** 向线程发送一个取消请求
**函数原型**:int pthread_cancel(pthread_t thread);
**参数:**
- thread:要发送请求的线程ID
**返回值:**
- 成功:返回 0
- 失败:返回 errno
### demo
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
void *tun(void *arg){
while(1){
printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
sleep(1);
}
return NULL;
}
int main(int argc, char *argv[]){
pthread_t tid;
int ret = pthread_create(&tid, NULL, tun, NULL);
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
sleep(5);
ret = pthread_cancel(tid);
if(ret != 0){
fprintf(stderr, "pthread_cancel error %s\n", strerror(ret));
}
ret = pthread_join(tid, NULL);
if(ret != 0){
fprintf(stderr, "pthread_join error %s\n", strerror(ret));
}
return 0;
}
```
<font color="#FF0000">**注意:**</font> 线程的取消并不是实时的,而是具有一定的延时性。需要等待线程到达某个取消点才可以结束。
**取消点** 是线程检查是否被取消,并按请求进行动作的一个位置,通常是一些系统调用 `creat`、`open`、`pause`...等。执行 `man 7 pause`可查看具备这些取消店的系统调用列表。
可粗略认定为,一个系统调用(进入内核)即为一个取消点。如果线程中没有取消点,例如上述 demo,在 tun 函数中,执行了 `sleep` 与 `printf` 函数,都会进行系统调用进入内核,从而使得该线程到达取消点,线程被结束。如果将两行注释,按照上述程序,主线程会继续向下执行,阻塞在 `pthread_join` 函数处,等待线程到达取消点,完成 `pthread_cancel` 发送的请求,从而回收线程。如果线程中没有取消点,可以通过调用 `pthread_testcancel()`函数,自行设置一个取消点
被取消的线程,退出值定义在 Linux 的 pthread 库中,常数 `PTHREAD_CANCELED`的值为 -1,可在头文件 `pthread.h` 中找到,因此通过 `pthread_join` 回收一个被 `pthread_cancel` 取消的线程时,得到的返回值为 `-1`
## 线程分离
**作用:** 使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,<font color="#FF0000">**线程分离的目的**</font> 是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
**函数原型:** int pthread_detach(pthread_t thread);
参数:
- thread:要分离的线程号
返回值:
- 成功:返回 0
- 失败:errno
### demo
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
void *tun(void *arg){
printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]){
pthread_t tid;
int ret = pthread_create(&tid, NULL, tun, NULL);
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
ret = pthread_detach(tid);// 线程分离,不阻塞,线程终止会自动清理,无需回收
if(ret != 0){
fprintf(stderr, "pthread_detach error %s\n", strerror(ret));
}
//在线程分离后,回收时,该函数直接返回错误,调用失败
ret = pthread_join(tid, NULL);
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
pthread_exit(0);
}
```
## 线程属性
Linux 下线程的属性是可以根据实际项目需要进行设置的,之前在创建线程的时候通常第二个参数 `attr` 通常传入 `NULL`,代表使用线程的默认属性,默认属性已经可以解决大多数开发时的问题,如果对程序的性能提出更高的需求,那么需要设置线程属性,比如设置线程的大小来降低内存的使用,增加最大线程的个数等
### 线程属性初始化
应先初始化线程属性,在创建线程
- 初始化线程属性
- int pthread_attr_init(pthread_attr_t *attr);
- 成功:0
- 失败:errno
- 销毁线程属性所占用的资源
- int pthread_attr_destroy(pthread_attr_t *attr);
- 同上
### 线程的分离状态
线程的分离状态决定一个线程以什么样的方式终止自己。
- **非分离状态:** 线程的默认属性时非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当调用 `pthread_join()`函数成功返回时,创建的线程才算终止,才能释放自己的系统资源(PCB)。
- **分离状态:** 分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源,应该根据自己的需要,选择适当的分离态
线程分离态的函数:
- 设置线程属性:分离 or 不分离
> int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- 获取线程属性:
> int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
参数:
- attr:初始化的线程属性
- detachstate:取值如下
- PTHREAD_CREATE_DETACHED(分离线程)
- PTHREAD_CREATE_JOINABLE(非分离线程)
这里需要注意的一点,
### demo
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
void *tun(void *arg){
return NULL;
}
int main(int argc, char *argv[]){
pthread_t tid;
pthread_attr_t attr;
int ret = pthread_attr_init(&attr); //初始化线程属性
if(ret != 0){
fprintf(stderr, "pthread_attr_init error %s\n", strerror(ret));
}
ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//设置线程属性为分离态
if(ret != 0){
fprintf(stderr, "pthread_attr_setdetachstate error %s\n", strerror(ret));
}
ret = pthread_create(&tid, &attr,tun, NULL);//创建线程,直接为分离态
if(ret != 0){
fprintf(stderr, "pthread_create error %s\n", strerror(ret));
}
//根据上文,当回收一个已经分离出去的线程,该函数会立即报错返回
//用来检验创建分离线程时候成功
ret = pthread_join(tid, NULL);
if(ret != 0){
fprintf(stderr, "pthread_join error %s\n", strerror(ret));
}
pthread_attr_destroy(&attr); //销毁资源
pthread_exit(0);
}
```
### 线程与进程函数控制原语对比
|功能|线程|进程|
|-------|-------|-------|
|创建 线/进 程|pthread_create()|fork() / vfork()|
|获取 线/进 程号|pthread_self()|getpid()|
|退出 线/进 程|pthread_exit()|exit() / _exit()|
|回收 线/进 程|pthread_join()|wait() / waitpid()|
|结束 线/进 程|pthread_cancel()|kill()|
|分离 线/进 程|pthread_detach()||
### 线程使用注意事项
1. 主线程退出其他线程不退出,主线程应调用 `pthread_exit` 而非 `return`
2. 避免产生僵尸线程
3. 被 join 线程可能在 join 函数返回前就释放完了自己的所有内存资源,所以不应当返回被回收线程栈中的值
4. malloc 与 mmap 申请的内存可以被其他线程释放
5. 应避免在多线程模型中调用 fork ,创建的子进程中,只有调用 fork 的线程存在,其它线程在子进程中均被 `pthread_exit`
6. 信号的复杂语义很难和多线程共存,应尽量避免在多线程中引入信号机制
Linux系统编程——线程的基本操作