目录
一、介绍
1.1 线程与进程的区别
进程
进程有独立的地址空间;
Linux为每个进程创建task_struct;
每个进程都参与内核调度,互不影响;
进程在切换时系统开销大。
线程
很多操作系统引入了轻量级进程(LWP);
同一进程中的线程共享相同地址空间。
-
1.2 线程特点
通常线程指的是共享相同地址空间的多个任务;
使用多线程的好处:
大大提高了任务切换的效率;
避免了额外的TLB & cache的刷新。
1.3 线程共享资源
一个进程中的多个线程共享以下资源:
- 可执行的指令
- 静态数据
- 进程中打开的文件描述符
- 当前工作目录
- 用户ID
- 用户组ID
-
1.4 线程私有资源
每个线程私有的资源包括:
- 线程ID (TID)
- PC(程序计数器)和相关寄存器
- 堆栈
- 错误号 (errno)
- 优先级
- 执行状态和属性
1.5 Linux线程库
pthread线程库中提供了如下基本操作
- 创建线程
- 回收线程
- 结束线程
同步和互斥机制
- 信号量
- 互斥锁
二、相关函数
2.1 线程创建 – pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread, const
pthread_attr_t *attr, void *(*routine)(void *), void *arg);
- 成功返回0,失败时返回错误码
- thread 线程对象
- attr 线程属性,NULL代表默认属性
- routine 线程执行的函数
- arg 传递给routine的参数 ,参数是void * ,注意传递参数格式。
2.2 线程结束 – pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
- 结束当前线程
- retval可被其他线程通过pthread_join获取
- 线程私有资源被释放
2.3 线程查看tid函数
#include <pthread.h>
pthread_t pthread_self(void);
2.4 线程回收
使用pthread_create创建的线程有两种状态:joinable和unjoinable。默认是joinable 状态。 对于一个默认属性的线程 A 来说,线程占用的资源并不会因为执行结束而得到释放,故需要针对这个问题进行处理。pthread_detach()和pthread_join()就是控制子线程回收资源的两种不同的方式。
2.4.1 使用pyhread_join进行线程回收
如果是joinable状态,则该线程结束后(通过pthread_exit结束或者线程执行体任务执行完毕)不会释放线程所占用堆栈和线程描述符等资源,需要在主线程调用了pthread_join函数之后才会释放。pthread_join函数一般应用在主线程需要等待子线程结束后才继续执行的场景(pthread_join是一个阻塞函数,调用方会阻塞到pthread_join所指定的tid的线程结束后才被回收)。
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
- 成功返回0,失败时返回错误码
- thread 要回收的线程对象
- 调用线程阻塞直到thread结束
- *retval 接收线程thread的返回值
2.4.2 使用线程分离进行线程回收
如果是unjoinable状态,则该线程结束后会自动释放占用资源。实现方式是在创建时指定属性,或者在线程执行体的最开始处添加一行:pthread_detach(pthread_self());这种方式不会阻塞,调用它后,线程运行结束后会自动释放资源,。
方式一:
int pthread_detach(pthread_t thread);
- 成功:0;失败:错误号
- 指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程)
方式二:
pthread_attr_t attr; /*通过线程属性来设置游离态(分离态)*/
//设置线程属性为分离
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
2.5 取消一个线程
2.5.1 取消一个线程
在程序运行过程中,存在向一个线程发送请求它立即退出的操作,这种操作被成为取消线程。
int pthread_cancel(pthread_t thread); 杀死一个线程
注:线程的取消要有取消点才可以,线程的取消点主要是阻塞的系统调用
2.5.2 手动设置取消点
如果没有取消点,手动设置一个
void pthread_testcancel(void);
2.5.3 取消状态以及类型
默认情况下,线程会响应其它线程发送的取消请求的,响应请求然后退出线程。当然,线程可以选择不被取消或者设置取消方式,通过 pthread_setcancelstate()和 pthread_setcanceltype()来设置线程的取消性状态和类型。
int pthread_setcancelstate(int state, int *oldstate);
PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE
- PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。
- PTHREAD_CANCEL_DISABLE:线程不可被取消,如果此类线程接收到取消请求,则会将请求挂起,直至线程的取消性状态变为 PTHREAD_CANCEL_ENABLE
int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED
PTHREAD_CANCEL_ASYNCHRONOUS
- PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程还是继续运行,取消请求被挂起,直到线程到达某个取消点为止,这是所有新建线程包括主线程默认的取消性类型(所谓取消点其实就是一系列函数,当执行到这些函数的时候,才会真正响应取消请求,这些函数就是取消点);
- PTHREAD_CANCEL_ASYNCHRONOUS:在其他时间点取消。
2.6 线程的清理
必要性: 当线程非正常终止,需要清理一些资源。
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
routine 函数被执行的条件:
- 被pthread_cancel取消掉。
- 执行pthread_exit
- 非0参数执行pthread_cleanup_pop()
注意:
- 必须成对使用,即使pthread_cleanup_pop不会被执行到也必须写上,否则编译错误。
- pthread_cleanup_pop()被执行且参数为0,pthread_cleanup_push回调函数routine不会被执行.
- pthread_cleanup_push 和pthread_cleanup_pop可以写多对,routine执行顺序正好相反
- 线程内的return 可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。
三、代码测试
3.1 线程的创建与参数传递
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *testThread(void *arg)
{
printf("This is a %s,pid=%d,tid=%lu\n",(const char*)arg,getpid(),pthread_self());
pthread_exit(NULL);
printf("after pthread exit\n");
}
int main()
{
pthread_t tid;
int ret;
ret = pthread_create(&tid,NULL,testThread,"new Thread");
printf("This is main thread,pid=%d,tid=%lu\n",getpid(),pthread_self());
sleep(1);
return 0;
}
实验截图
从实验截图可以看出,线程已经成功创建,并且可以看到,主线程和子线程都为同一个pid进程下,但是有各自的进程tid 。
3.2 线程回收
3.2.1 使用pthread_join进行线程回收
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg)
{
printf("This is child thread\n");
sleep(25);
pthread_exit("thread return");
}
int main()
{
pthread_t tid[100];
void *retv;
int i;
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,func,NULL);
}
for(i=0;i<5;i++)
{
pthread_join(tid[i],&retv);
printf("thread ret=%s\n",(char*)retv);
}
while(1)
{
sleep(1);
}
return 0;
}
实验截图
从实验截图可以看出,子线程在结束后,成功被pthread_join回收。
3.2.2 通过pthread_detach进行线程回收
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg)
{
printf("This is child thread\n");
sleep(25);
pthread_exit("thread return");
}
int main()
{
pthread_t tid[100];
void *retv;
int i;
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,func,NULL);
pthread_detach(tid[i]);
}
while(1)
{
sleep(1);
}
return 0;
}
3.2.3 通过线程属性进行线程回收
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg)
{
printf("This is child thread\n");
sleep(25);
pthread_exit("thread return");
}
int main()
{
pthread_t tid[100];
void *retv;
int i;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,func,NULL);
}
while(1)
{
sleep(1);
}
return 0;
}