一、什么是线程
1、LWP:light weight process 轻量级的进程,本质仍是进程(在 Linux 环境下)
2、进程:有独立地址空间,拥有 PCB
3、线程:也有 PCB,但没有独立的地址空间(共享)
4、区别:在于是否共享地址空间。例如:独居(进程),合租(线程)。
5、在 Linux 线程是是 CPU 调度和执行的最小单位,进程是系统分配资源和调度的最小单位,可以看成只有一个线程的进
二、Linux 内核线程实现原理
1、轻量级进程(light-weight process ),也有 PCB,创建进程使用的底层函数和进程一样,都是 clone。
2、从内核里看进程和线程是一样的,都有各自不同的 PCB ,但是 PCB 中指向内存资源的三级页表是相同的。
3、进程可以蜕变成线程。
4、线程可看做栈和寄存器的集合。
5、在 Linux 下,线程是最小的执行单位;进程是最小的资源分配单位。
查看 LWP 号: ps -Lf pid 查看指定线程的 lwp 号
三、线程共享资源
1、文件描述符。
2、每种信号的处理方式。
3、当前工作目录。
4、用户 ID 和组 ID。
5、内存地址空间(.text/.data/.bss/heap/共享库)
四、线程非共享资源
1、线程 id
2、处理器现场和栈指针(内核栈)
3、独立的用户空间(用户空间栈)
4、errno变量
5、信号屏蔽字
6、调度优先级
五、线程优、缺点
1、优点:
(1)、提高程序并发性
(2)、开销小
(3)、数据通信、共享数据方便
2、缺点:
(1)、库函数、不稳定
(2)、调试、编写困难、gdb 调试不支持
(3)、对信号支持不好
六、线程控制原语
1、pthread_self 函数
作用: 获取线程 ID ,其作用对应进程中的 getpid() 函数。
函数原型:
#include
pthread_t pthread_self(void);
返回值:
成功,返回 0 ;失败:无返回值。
线程 ID: pthread_t 类型,本质:在 Linux下无符号整数 (%lu),其他系统中可能是结构体实现。
线程 ID 是进程内部,识别标志。(两个进程,允许线程 ID 相同)
注意:
不应使用全局变量 pthread_t tid,在子线程中通过 pthread_create 传出参数来获取线程 ID,而应使用 pthread_self。
#include
#include
#include
#include
#include
void *thd_func(void *arg)
{
printf("In thread id = %lu,pid = %u
",pthread_self(),getpid());
return NULL;
}
int main(void)
{
pthread_t tid;
int ret;
printf("In main 1: id = %lu,pid = %u
",pthread_self(),getpid());
ret = pthread_create(&tid,NULL,thd_func,NULL);
if( ret !=0 )
{
fprintf(stderr,"pthread_create error:%s
",strerror(ret));
exit(1);
}
sleep(1);
printf("In main2: id = %lu,pid = %u
",pthread_self(),getpid());
return 0;
}
2、pthrea_create函数
作用:创建一个新的线程,其作用对应 进程中的 fork () 函数。
函数原型:
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
返回值:
成功:返回 0 ;失败:返回错误号 --------------------------- 在Linux 下所有和线程相关的控制原语失败均返回 错误号。
参数:
pthread_t:当前Linux 中可以理解为: typedef unsigned long int pthread_t
参数1:传出参数,保存系统为我们分配好的线程 ID。
参数2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3:函数指针,指向主线程函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数。
在一线程中调用 pthread_create() 创建新的线程后,当线程从 pthread_create() 返回继续往下执行,而新的线程所执行的代码由我们所传给 pthread_create()的函数指针决定。pthread_create 创建成功返回后,新创建的线程 ID 被填写到 thread 参数所指向的内存单元。
attr 参数表示线程属性。
循环创建 N 个线程,示例代码:
/*
循环创建 N 个子进程
*/
#include
#include
#include
#include
#include
void *thd_func(void *arg)
{
int i = (int)arg;
sleep(i);
printf("%dthd id = %lu,pid = %u
",i+1,pthread_self(),getpid());
return NULL;
}
int main(void)
{
pthread_t tid;
int ret;
int i;
for( i=0;i<5;i++ )
{
ret = pthread_create(&tid,NULL,thd_func,(void*)i);
if( ret !=0 )
{
fprintf(stderr,"pthread_create error:%s
",strerror(ret));
exit(1);
}
}
sleep(i);
return 0;
}
3、线程共享:
线程间共享全局变量。
注意:线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量。只能借助 mmap
4、pthread_exit 函数
作用:将单个线程退出
函数原型:
#include
void pthread_exit(void *retval);
参数:
retval 表示线程退出状态,通常传 NULL。
注意:
线程中禁止使用 exit 函数,会导致进程内的所有线程全部退出。在多线程环境中,应尽量少用或者不用 eixt 函数。取而代之,使用 pthread_exit 函数,将单个线程退出。任何线程里 exit 导致进程退出,其他线程未工作结束,主控线程退出时不能 return 或 exit。
pthread_exit 或者 return
返回的指针所指向的内存单元必须是全局的或者是 malloc 分配的,不能再线程函数的栈上分配,因为当其他函数得到这个返回指针时线程函数已经退出了。
5、pthread_join 函数
作用:阻塞等待线程退出,获取线程退出状态 ,其作用对应于进程中的 waitpid()函数。
函数原型:
#include
int pthread_join(pthread_t thread, void **retval);
返回值:
成功:返回 0 ; 失败:返回错误号
参数:
thread 线程 ID(注意不是指针)
retval 存储线程结束状态。
对比进程和线程记忆:
进程中: main 返回值、exit 参数 ------> int; 等待子进程结束 wait 函数参数 ------> int *
线程中: 线程主函数返回值、pthread_exit ---------->void *;等待子线程结束 pthread_join 函数参数 ---------->void **
#include
#include
#include
#include
#include
typedef struct
{
char ch;
int var;
char str[64];
}exit_t;
void *thd_func(void *arg)
{
exit_t *retval = (exit_t*)arg;
retval->ch = 'm';
retval->var = 200;
strcpy(retval->str,"my Thread
");
pthread_exit((void*)retval);
return NULL;
}
int main(void)
{
pthread_t tid;
int ret;
exit_t *retval = (exit_t*) malloc(sizeof(exit_t));
ret = pthread_create(&tid,NULL,thd_func,(void*)retval);
if( ret !=0 )
{
fprintf(stderr,"pthread_create error:%s
",strerror(ret));
exit(1);
}
pthread_join(tid,(void**)&retval);
printf("ch = %c,var = %d,str = %s
",retval->ch,retval->var,retval->str);
free(retval);
return 0;
}
6、pthread_detach 函数
作用:实现线程分离
函数原型:
#include
int pthread_detach(pthread_t thread);
返回值:
成功,返回0;失败:返回错误号
注意:
(1)、线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务中常用。
(2)、进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源人存留在系统中,导致内核认为该进程仍存在。
(3)、也可使用 pthread_create 函数参数 2 (线程属性)来设置线程分离。
#include
#include
#include
#include
#include
void *thd_func(void *arg)
{
int n = 3;
while(n--)
{
printf("thread count = %d
",n);
sleep(1);
}
// return (void*)1;
pthread_exit((void*)1); // return 和 pthread_exit 的作用相同
}
int main(void)
{
pthread_t tid;
void *tret;
int err;
pthread_create(&tid,NULL,thd_func,NULL);
pthread_detach(tid); // 让线程分离--------自动退出,无系统残留资源
while(1)
{
err = pthread_join(tid,&tret);
printf("-------------- err = %d
",err);
if( err != 0 )
fprintf(stderr,"thread_join error: %s
",strerror(err));
else
fprintf(stderr,"thread exit code %d
",(int)tret);
sleep(1);
}
return 0;
}
7、pthread_cancel 函数
作用 ;杀死(取消)线程,其作用对应于进程中的 kill() 函数。
函数原型:
#include
int pthread_cancel(pthread_t thread);
返回值:
成功:返回 0;失败:返回错误号
注意:
(1)、线程的取消不是实时的,而有一定的时延。需要等待线程达到某个取消点(检查点)。
(2)、取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 create、open、pause、close、read、write......执行命令 man 7
threads 可查看具备这些取消点的系统调用列表。
(3)、可以粗略的认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用 pthread_testcancel 函数自行设置一个取消点。
(4)、被取消的线程,退出定义在 Linux 的 pthread 库中,常数 PTHREAD_CANCEL 的值是 -1 ,可在头文件 pthread.h 中只找到它的定义
#define
PTHREAD_CANCEL ((void*)-1)。因此,当我们对一个已经被取消的线程使用 pthread_join 回收时,得到的返回值为 -1 。
七、线程使用注意事项h
1、主线程退出时其他线程不退出,主线程应该调用 pthread_exit
2、避免僵尸线程
pthread_join()
pthread_detach()
pthread_create() 指定分离属性
被 Join 线程肯能在 join 函数返回前就释放自己的所有内存资源,所以不应当返回被回收线程中的值。
3、malloc 和 mmap 申请的内存可以被其他线程释放
4、应避免在多线程模型中调用 fork 除非,马上 exec ,子进程中只有调用 fork 的线程存在,其他线程在子进程中均 pthread_exit
5、信号的复杂语义很难和多线程共存,应避免在多线程中引入信号机制。