6. 线程
什么是线程?
LWP: light weight process 轻量级的进程,本质仍是进程(Linux环境下)
进程:独立空间地址,拥有PCB
线程:也有PCB,但是没有独立的地址空间(共享)
区别:在于是否共享空间地址
Linux下:线程:最小的执行单位
进程:最小的分配资源单位,可以看成是一个线程的进程
Linux内核线程实现原理:
- 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是
clone()
- 从内核里看进程和线程是一样的,都有各自不同的PCB
- 进程至少有一个线程
- 线程可以看作寄存器和栈的集合
- 在linux下,线程是最小的执行单位;进程是最小的资源分配单位
查看LWP(线程编号)号:
ps -Lf pid
查看指定线程的LWP号
线程共享资源
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID
- 内存地址空间(.text/.data/.bss/heap/共享库)
线程非共享资源
- 线程ID
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量
- 信号屏蔽字
- 调度优先级
获得错误码对应的错误信息:
char *strerror(int errnum);
线程的优点:
- 提高并发性
- 占用资源量小
- 通信方便
缺点
- 调试困难
- 库函数不稳定
- 对信号支持不好
有于Linux系统早期并没有线程,是在后期版本中加入,由于实现方法导致进程和线程差别不是很大
6.1 线程控制原语
6.1.1 获取线程IDpthread_self()
帮助手册:
man pthread_self
包含头文件:
#include <pthread.h>
函数原型:
pthread_t pthread_self(void);
函数说明:
获取线程ID.其作用对应进程中的
getpid()
函数。
线程ID:pthread_t
类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
线程ID是进程内部识别标志。(两个进程间,线程ID允许相同)
注意:不应使用全局变量pthread_t tid
,在子线程中通过pthread_create
传出参数来获取线程ID,而应使用pthread_self
编译和链接时需加上-lpthread
参数 | 说明 |
---|---|
return | 返回调用线程的ID |
6.1.2 创建一个新线程pthread_create()
帮助手册:
man pthread_create
包含头文件:
#include <pthread.h>
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
函数说明:
创建一个新线程。其作用,对应进程中
fork()
函数编译和链接时需加上
-lpthread
参数 | 说明 |
---|---|
thread | 新线程ID(传出) |
attr | 线程的属性信息(传入) |
start_routine | 线程执行的函数 |
arg | 函数执行的参数 |
return | 成功:0 失败:errno |
示例:创建一个线程:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
void* thr(void *arg)
{
printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
printf("I am a main thread, pid=%d, tid=%lu\n", getpid(), pthread_self());
sleep(1);
return 0;
}
运行结果:
6.1.3 终止调用线程pthread_exit()
帮助手册:
man pthread_exit
包含头文件:
#include <pthread.h>
函数原型:
void pthread_exit(void *retval);
编译和链接时需加上
-lpthread
参数 | 说明 |
---|---|
retval | 退出值(传入) |
6.1.4 回收线程pthread_join()
帮助手册:
man pthread_join
包含头文件:
#include <pthread.h>
函数原型:
int pthread_join(pthread_t thread, void **retval);
函数说明:
阻塞等待回收
编译和链接时需加上
-lpthread
参数 | 说明 |
---|---|
thread | 创建线程时传出的thread |
retval | 退出值(传出) |
return | 成功:0 失败:errno |
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* thr(void *arg)
{
printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
pthread_exit((void*)100);
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
printf("I am a main thread, pid=%d, tid=%lu\n", getpid(), pthread_self());
void *ret;
pthread_join(tid, &ret);
printf("ret = %d\n", (int)ret);
pthread_exit(NULL);
}
运行结果:
6.1.5 杀死线程pthread_cancel()
帮助手册:
man pthread_cancel
包含头文件:
#include <pthread.h>
函数原型:
int pthread_cancel(pthread_t thread);
函数说明:
线程的取消并不是实时的,而是有一定的延时。需要等待线程到达某个取消点(检查点)。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write…man 7 pthreads
可以查看具备这些取消点的系统调用列表。
可以粗略的认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthrestcancel()
函数自行设置一个取消点。
被取消的线程,退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED
的值是-1.可以在头文件pthread.h
中找到他的定义#define PTHREAD_CANCELED ((void *)-1)
。编译和链接时需加上
-lpthread
参数 | 说明 |
---|---|
thread | 需要杀死的进程 |
return | 成功:0 失败:errorno |
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* thr(void *arg)
{
while(1)
{
// 如果把printf和sleep注释,由于没有取消点,线程无法被杀死
printf("tid = %lu\n", pthread_self());
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
sleep(3);
pthread_cancel(tid);
void* ret;
pthread_join(tid, &ret);
printf("thread exit with %d\n", (int)ret);
return 0;
}
运行结果:
6.1.6 线程分离pthread_detach()
帮助手册:
man pthread_detach
包含头文件:
#include <pthread.h>
函数原型:
int pthread_detach(pthread_t thread);
函数说明:
- 线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
- 进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留的资源仍然存于系统中,导致内核认为该进程存在。
- 也可使用
pthread_create()
函数第二个参数(线程属性)来设置线程分离。- 一般情况下,线程终止后,其终止状态一直保留到其他线程调用
pthread_join()
获取他的状态为止。但是线程也可以被设置为detach状态,这样的线程一旦终止就立刻回收他占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL
错误。
参数 | 说明 |
---|---|
thread | 需要被分离的线程 |
return | 成功:0 失败:errorno |
示例:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
int val = 100;
void *thr(void* arg)
{
printf("I am a thread. tid = %lu, val = %d\n", pthread_self(), val)
sleep(2);
val = 1001;
printf("I am a thread. tid = %lu, val = %d\n", pthread_self(), val);
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
pthread_detach(tid);
printf("I am main thread. tid = %lu, val = %d\n", pthread_self(), val);
val = 1003;
sleep(5);
printf("I am main thread. tid = %lu, val = %d\n", pthread_self(), val);
int ret = 0;
if((ret = pthread_join(tid, NULL)) > 0)
{
printf("join err, ret = %d, %s\n", ret, strerror(ret));
}
pthread_exit(NULL);
}
运行结果(线程共享全局变量):
6.1.7 比较两个线程ID是否相等pthread_equal()
帮助手册:
man pthread_equal
包含头文件:
#include <pthread.h>
函数原型:
int pthread_equal(pthread_t t1, pthread_t t2);
编译和链接时需加上
-lpthread
参数 | 说明 |
---|---|
t1 | 第一个线程ID |
t2 | 第二个线程ID |
return | 相同:非0 不同:0 |
示例创建多个线程:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thr(void* arg)
{
int num = (int)arg;
printf("I am %d thread, self = %lu\n", num, pthread_self());
return (void*)(100 + num);
}
int main()
{
pthread_t tid[5];
int i;
for(i = 0;i < 5; ++i)
{
pthread_create(&tid[i], NULL, thr, (void*)i);
}
for(i = 0;i < 5;++i)
{
void* ret;
pthread_join(tid[i], &ret);
printf("i = %d, ret = %d\n", i, (int)ret);
}
return 0;
}
运行结果:
6.2 线程属性
6.2.1 线程属性结构体:
typedef struct
{
int etachstate; // 线程的分离状态
int schedpolicy; // 线程调度策略
struct sched_param schedparam; // 线程的调度参数
int inheritsched; // 线程的继承性
int scope; // 线程的作用域
size_t guardsize; // 线程栈末尾的警戒缓冲区大小
int stackaddr_set; // 线程的栈设置
void* stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈的大小
}pthread_attr_t;
6.2.2 线程的分离状态:
- 线程的分离状态决定一个线程以什么样的方式来终止自己。
- 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当
pthread_join()
函数返回时,创建的线程才算终止,才能释放自己占用的资源。- 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放资源。应该根据自己的需要,选择适当的分离状态。
- 线程分离状态的函数:
- 线程设置属性,分离或分分离
–int pthread_attr_setdetachstate(pthread_attr_t * arrt, int detachstate);
- 获取线程属性,分离或非分离
–int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
参数 | 说明 |
---|---|
attr | 已初始化的线程属性 |
detachstate | PTHREAD_CREATE_DETACHED(分离线程) PTHREAD_CREATE_JOINABLE(非分离线程) |
6.2.3 初始化线程的属性pthread_attr_init()
帮助手册:
man pthread_attr_init
包含头文件:
#include <stdio.h>
函数原型:
int pthread_attr_init(pthread_attr_t *attr);
6.2.4 销毁线程的属性pthread_attr_destroy()
帮助手册:
man pthread_attr_destroy
包含头文件:
#include <stdio.h>
函数原型:
int pthread_attr_destroy(pthread_attr_t *attr);
6.2.5 设置属性分离态pthread_attr_setdetachstate()
帮助手册:
man pthread_attr_setdetachstate
包含头文件:
#include <stdio.h>
函数原型:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
6.2.6 获取属性分离态pthread_attr_getdetachstate()
帮助手册:
man pthread_attr_getdetachstate
包含头文件:
#include <stdio.h>
函数原型:
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
示例:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void *thr(void *arg)
{
printf("I am a thread\n");
return NULL;
}
int main()
{
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, thr, NULL);
int ret = 0;
if((ret = pthread_join(tid, NULL)) > 0)
{
printf("join err:%d, %s\n", ret, strerror(ret));
}
pthread_attr_destroy(&attr);
return 0;
}
运行结果:
6.3 线程使用注意事项
主线程退出其他线程退出,主线程因调用
pthread_exit()
避免僵尸线程
pthread_join()
pthread_detach()
pthread_create()
指定分离属性
被join
线程可能在join
函数返回前就释放完自己的所有资源,所以不应当返回被回收线程栈中的值
malloc
和mmap
申请的内存可以被其他线程释放应避免在多线程模型中调用
fork
,除非马上exec
,子进程中只用调用fork
的线程存在,其他线程在子进程中均pthread_exit
信号复杂语义很难和多线程共存,应避免在多线程引入信号机制
创建线程数:CPU核数*2+2