线程控制原语
线程函数的程序在 pthread
库中,故链接时要加上参数 。 -lpthread
选项
在线程中检查错误的方法应为
fprintf(stderr, "xxx error: %s\n", strerror(ret));
查看线程ID
作用: 查看当前线程ID
函数原型: pthread_t pthread_self(void);
返回值: 线程ID
线程 ID:pthread_t 类型,在 Linux 下为无符号整数数%lu
,在其他系统中可能是结构体实现
线程 ID 是 进程内部的识别标志,两个进程间,线程 ID 允许相同。
线程创建
函数原型:
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
#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;
}
循环创建多个线程
#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
#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
#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;
}
循环创建多个线程,并且回收
#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
#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;
}
注意: 线程的取消并不是实时的,而是具有一定的延时性。需要等待线程到达某个取消点才可以结束。
取消点 是线程检查是否被取消,并按请求进行动作的一个位置,通常是一些系统调用 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
线程分离
作用: 使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的 是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
函数原型: int pthread_detach(pthread_t thread);
参数:
- thread:要分离的线程号
返回值:
- 成功:返回 0
- 失败:errno
demo
#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
#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() |
线程使用注意事项
- 主线程退出其他线程不退出,主线程应调用
pthread_exit
而非return
- 避免产生僵尸线程
- 被 join 线程可能在 join 函数返回前就释放完了自己的所有内存资源,所以不应当返回被回收线程栈中的值
- malloc 与 mmap 申请的内存可以被其他线程释放
- 应避免在多线程模型中调用 fork ,创建的子进程中,只有调用 fork 的线程存在,其它线程在子进程中均被
pthread_exit
- 信号的复杂语义很难和多线程共存,应尽量避免在多线程中引入信号机制