线程就是 Light weight process ,LWP,轻量级进程,在Linux环境下它仍然是进程,一个进程内部可以有多个线程,默认情况下一个进程内部有一个线程。不同的是,进程有自己的进程控制块PCB,并且拥有自己独立的地址空间;而线程虽然也有线程控制块(这样来看,如果一个进程内有多个线程,那么进程内将有多个PCB),但是它没有独立的地址空间,而是共享空间,我们可以理解为在进程的虚拟空间中除了栈都是共享的(在实际编程中,线程一般就是一个函数,函数肯定要有自己的栈来运行)。也就是说,进程和线程最大的区别在于是否共享地址空间。在Linux环境下,线程是最小的执行单位,进程是最小的资源分配单位。
我们在进程间通信的时候,因为每个进程都有自己的进程地址空间,所以才要通过信号、管道等去传递数据。而线程共享存储空间(除栈外),所以通信就方便多了,在进程中定义一个全局变量就可以让所有线程去共享,来实现通信。通过线程可以把任务分解,同一个进程中不同线程同时执行不同的任务,大大提供了执行效率。但是,如果当前计算机只有一个CPU核心,多线程也就没意义了,因为线程执行的时候都要持有CPU。
在类Uinx系统中,早期是没有线程概念的,直到80年代才引入,借助进程机制实现出了线程的概念,因此在类Uinx系统中,线程和进程密切相关。
优点是可以提高程序的并发性,有效提高CPU利用率;并且开销小,多个线程共享进程的空间,数据之间的通信和共享更方便。
缺点是调试和编写工作繁琐,并且线程相关的库函数不稳定,对信号机制的支持不友好。这里要注意,线程都是库函数,在编译的时候都要加上一个参数 -pthread。
因为线程都是库函数,所以编译的时候都要加一个-pthread选项,表示包含线程库,总之,编译线程相关的程序都要在gcc后面加参数 -pthread。Compile and link with -pthread. (有的时候是加-lpthread)
#include <pthread.h>
pthread_t pthread_self(void);
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
示例:创建一个线程
/************************************************************
>File Name : pth_test.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* th_print(void* arg)
{
printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, th_print, (void*)argv[1]);
printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
sleep(1); /*不睡眠的话,进程退出了,线程还没结束就没了*/
return 0;
}
可以看到进程ID是一样的,线程ID不同。
#include <pthread.h>
void pthread_exit(void *retval);
示例:线程的三种退出方式
/************************************************************
>File Name : pth_test2.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* th_print(void* arg)
{
printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
/*return NULL; */
pthread_exit(NULL);
/*exit(1); */
/*三种退出方式是不一样的,前两个都是正常退出,只退出线程。
线程中return表示退出线程,主线程中return代表退出进程。
pthread_exit表示退出线程。
而exit()是退出整个进程,所以在线程中应该使用前两种*/
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, th_print, (void*)argv[1]);
printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
sleep(5);
printf("sleep end...\n");
pthread_exit(NULL);
return 0;
}
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
示例:线程回收
/************************************************************
>File Name : join_test.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* th_print(void* arg)
{
printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
sleep(3);
printf("th_print thread end\n");
return (void*)101;
/*pthread_exit((void*)101); 等效*/
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, th_print, (void*)argv[1]);
printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
void* ret;
pthread_join(tid, &ret); /*线程回收函数是阻塞等待的
只有等到线程结束了,才会执行下面的打印语句*/
printf("th_print thread return: %d\n", (int)ret);
return 0;
}
编译执行,可以看到线程退出信息被返回。
#include <pthread.h>
int pthread_detach(pthread_t thread);
线程分离状态是指:指定该状态,那么线程将主动与主控线程断开关系。等到线程结束后,其退出状态不由其它线程获取,而是直接自己自动释放,多用于网络、多线程服务器等场合。设置线程分离既可以使用pthread_detach()函数,也可以使用pthread_create()函数参数attr来设置线程分离属性。
如果进程有线程分离机制,那么就不会再产生僵尸进程。僵尸进程的产生主要是由于进程终止后,大部分资源被释放,但是仍然有残留西元存在于系统中,导致内核认为该进程仍然存在。
一般情况下,线程终止后,其终止状态一直保留到其他线程调用pthread_join()获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join(),因为这样调用会返回EINVAL错误。也就是说,如果一个线程已经调用了pthread_join()就不能再调用pthread_join()函数了。
/************************************************************
>File Name : detach_test.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* th_print(void* arg)
{
printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
sleep(3);
printf("th_print thread end\n");
return (void*)101;
/*pthread_exit((void*)101); 等效*/
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, th_print, (void*)argv[1]);
printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
pthread_detach(tid);
sleep(6);
int ret = 0;
if((ret = pthread_join(tid, NULL)) > 0)
{
printf("pthread_join errno: %d, %s\n", ret, strerror(ret));
}
return 0;
}
编译运行,可以看到pthread_join报错。
线程的分离状态决定一个线程以什么样的方式来终止自己。
设置分离状态的函数
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr); /*初始化属性*/
int pthread_attr_destroy(pthread_attr_t *attr); /*销毁属性*/
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
/*
PTHREAD_CREATE_DETACHED 设置分离
PTHREAD_CREATE_JOINABLE 需要pthread_join回收
*/
示例:设置分离状态属性
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* th_print(void* arg)
{
printf("th_print...\n");
return NULL;
}
int main()
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t tid;
pthread_create(&tid, &attr, th_print, NULL);
int ret;
if((ret = pthread_join(tid, NULL)) > 0)
{
printf("pthread_join errno: %d, %s\n", ret, strerror(ret));
}
pthread_attr_destroy(&attr);
return 0;
}
需要注意的是,如果设置一个线程为分离状态,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止后就可能将线程号和系统资源移交给其它线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况的话可以采取一定的同步措施。最简单的方法之一就是在被创建的线程里调用pthread_cond_timedwait函数,让线程多等待一会,留出足够的时间让函数pthread_create返回。但是不能使用wait函数,因为它是让整个进程睡眠,无法达到线程同步的目的。
#include <pthread.h>
int pthread_cancel(pthread_t thread);
/************************************************************
>File Name : join_test.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* th_print(void* arg)
{
while(1)
{
printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
sleep(1);
}
pthread_exit((void*)101);
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, th_print, (void*)argv[1]);
printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
sleep(3);
pthread_cancel(tid); /*杀死线程*/
void* ret;
pthread_join(tid, &ret);
printf("th_print thread return: %d\n", (int)ret);
return 0;
}
杀死线程的返回值是-1
需要注意的是,线程取消并不是实时的,而是具有一定的延时,需要等待线程达到某个取消点(检查点)。就比如说我们停车,必须到达停车位才能停车,杀死线程的时候也并不是立马就杀死,而是等到达取消点了才能杀死。比如说在上面的程序中,如果我们th_print()函数改造为一个空虚循环,这个先杀就无法使用pthread_cancel()杀死了,因为没有取消点。
void* th_print(void* arg)
{
while(1)
{
;
}
pthread_exit((void*)101);
}
取消点就是线程检查是否被取消,并按照请求进行动作的一个位置。通常是一些系统调用creat、open、pause、close、read、write等,通过 man 7 pthreads 可以查看具备这些取消点的系统调用列表。可以认为,一个系统调用(陷入内核)就是一个取消点。如果没有取消点的话,可以通过调用 pthread_testcansel() 函数自行设置一个取消点。被取消的线程退出值定义在Linux的pthread库中,常数PTHREAD_CANCELED的值是-1,在头文件pthread.h中它的定义为 #define PTHREAD_CANCELED ((void*)-1),也就是说,使用pthread_join回收被取消的线程时,得到的返回值是-1。
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void th_func(void* num) /*不稳定*/
{
int ret = (int)num;
printf("thread %d, tid: %d\n", num, pthread_self());
return (void*)(ret+10);
}
int main(int argc, char* argv[])
{
pthread_t tid[5];
int i;
for(i = 0; i < 5; i++)
{
pthread_create(&tid[i], NULL, th_func, (void*)i); /*传地址不可以*/
}
for(i = 0; i < 5; i++)
{
void* ret;
pthread_join(tid[i], &ret);
printf("thread %d, ret: %d\n", i, (int)ret);
}
return 0;
}