线程
线程与进程的区别
在linux系统当中,进程与线程的相同点大于不同点,系统并没有为线程定义特殊的数据结构,(所有的线程或进程的核心数据结构都是 task_struct)。唯一的区别就是共享的数据区域不同。
换句话说,线程看起来跟进程没有区别,只是线程的某些数据区域和其父进程是共享的,而子进程是拷贝副本,而不是共享。
所以总结来说就是线程就是数据共享了的进程
线程和进程的区别
1、进程是资源分配最小单位,线程是程序执行的最小单位;
2、线程间的切换效率相比进程间的切换要高
3、进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
4、创建一个线程比进程开销小;
5、线程占用的资源要⽐进程少很多。
6、线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
7、多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
8、进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;
线程的创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg);
//成功时返回0,失败时返回错误代码
thread是线程ID
attr 线程属性,NULL代表默认属性
routine 线程执行的函数
arg 传递给routine的参数 ,参数是void * ,注意传递参数格式
获取线程ID
如何获取线程ID?有两种方式
1)gettid或者类似gettid()的方法,tid=gettid();
2)直接调用pthread_self(),pid=pthread_self();
gettid 获取的是内核中线程ID,而pthread_self 是posix描述的线程ID。
对于单线程的进程,这两种方式获取的ID都是相同的,对于多线程的进程,这些线程的pid相同,tid不同。
示例如下:
#include<pthread.h> //创建线程的头文件
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h> //进程退出函数的头文件
void *fun(void *);
int main()
{
pthread_t id=pthread_self();
printf("main start\n");
int thread;
if((thread=pthread_create(&id,NULL,fun,NULL))!=0)
{
printf("pthraead_create:failed\n");
}
int i=0;
for(;i<4;i++)
{
printf("main runing\n");
sleep(1);
}
printf("main over\n");
exit(0);
return 0;
}
void *fun(void *arg)
{
printf("fun start\n");
int i=0;
for(;i<6;i++)
{
printf("fun runing\n");
sleep(1) //第一次运行,没有这一句;第二次运行时,有这一句
}
printf("fun over!\n");
}
运行结果如下:
第一次:没有加sleep(1);即fun()函数可以很快的运行完。
第二次加了sleep(1);即fun()函数和主函数一个接一个交叉输出。
可以得到
1)创建线程并执行线程函数,和调用函数是完全不同的概念。
2)主线程和函数线程是并发执行的。
3)线程提前于主线程结束时,不会影响主线程的运行
4)主线程提前于线程结束时,整个进程都会结束,其他线程也会结束
5)创建函数线程后,哪个线程先被执行是有操作系统的调度算法和机器环境决定。
线程结束的方式
线程结束执行的方式共有 3 种,分别是:
1、线程将指定函数体中的代码执行完后自行结束;
2、线程执行过程中,遇到 pthread_exit() 函数结束执行。
3、线程执行过程中,被同一进程中的其它线程(包括主线程)强制终止;
线程终止(自己终结自己)
线程被创建之后,如果要想让进程不结束,只有线程结束的话,要使用线程终止函数。这属于上述结束方式的第二种。
#include <pthread.h>
void pthread_exit(void *retval);
参数:retval是返回信息,"临终遗言",可以给可以不给
该变量不能使用临时变量。
可使用:全局变量、堆上开辟的空间、字符串常量。
线程取消(别人取消他)
这属于上述结束方式的第三种。
多线程程序中,一个线程可以借助 pthread_cancel() 函数向另一个线程发送“终止执行”的信号(后续称“Cancel”信号),从而令目标线程结束执行。
int pthread_cancel(pthread_t thread);
参数 thread 用于指定发送 Cancel 信号的目标线程。
如果 pthread_cancel() 函数成功地发送了 Cancel 信号,返回数字 0;反之如果发送失败,函数返回值为非零数。对于因“未找到目标线程”导致的信号发送失败,函数返回 ESRCH 宏(定义在<errno.h>头文件中,该宏的值为整数 3)。
注意,pthread_cancel() 函数的功能仅仅是向目标线程发送 Cancel 信号,
至于目标线程是否处理该信号以及何时结束执行,由目标线程决定。
对于接收 Cancel 信号后结束执行的目标线程,等同于该线程自己执行如下语句:pthread_exit(PTHREAD_CANCELED);
示例如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> // sleep() 函数
//线程执行的函数
void * thread_Fun(void * arg) {
printf("新建线程开始执行\n");
sleep(10);
}
int main()
{
pthread_t myThread;
void * mess;
int value;
int res;
//创建 myThread 线程
res = pthread_create(&myThread, NULL, thread_Fun, NULL);
if (res != 0) {
printf("线程创建失败\n");
return 0;
}
sleep(1);
//向 myThread 线程发送 Cancel 信号
res = pthread_cancel(myThread);
if (res != 0) {
printf("终止 myThread 线程失败\n");
return 0;
}
//获取已终止线程的返回值
res = pthread_join(myThread, &mess);
if (res != 0) {
printf("等待线程失败\n");
return 0;
}
//如果线程被强制终止,其返回值为 PTHREAD_CANCELED
if (mess == PTHREAD_CANCELED) {
printf("myThread 线程被强制终止\n");
}
else {
printf("error\n");
}
return 0;
}
运行结果如下:
线程等待(别人等待他运行以及回收资源)
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数描述:
参数 | 解释 |
---|---|
thread | 要等待的线程标识符 |
retval | 接收返回值:若是return退出的,接受入口函数的返回值,pthread_exit退出的,接受该函数的参数,pthread_cancel退出的,void** 保存的是PTHREAD_CANCLED = (void *)-1 |
为什么要有线程等待
1、线程等待和进程等待的不同?
第一点不同之处是进程之间的等待只能是父进程等待子进程, 而线程则不然。线程组内的成员是对等的关系, 只要是在一个线程组内, 就可以对另外一个线程执行连接(join) 操作。
第二点不同之处是进程可以等待任一子进程的退出 , 但是线程的连接操作没有类似的接口, 即不能连接线程组内的任一线程, 必须明确指明要连接的线程的线程ID。
2、为什么要有线程等待?
(1)如果没有线程等待,那么我们在前面知道,如果进程结束,也就是主线程结束,整个程序就结束了,程序里面的其他线程就没有执行的机会了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
(2)可以回收线程资源
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable(合并)的。
#include<pthread.h> //创建线程的头文件
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h> //进程退出函数的头文件
void *fun(void *);
int main()
{
pthread_t id=pthread_self();
printf("main start\n");
int thread;
if((thread=pthread_create(&id,NULL,fun,NULL))!=0)
{
printf("pthraead_create:failed\n");
}
pthread_join(id,NULL);//线程等待
int i=0;
for(;i<4;i++)
{
printf("main runing\n");
sleep(1);
}
printf("main over\n");
exit(0);
return 0;
}
void *fun(void *arg)
{
printf("fun start\n");
int i=0;
for(;i<6;i++)
{
printf("fun runing\n");
sleep(1)
}
printf("fun over!\n");
pthread_exit(NULL);
}
当加入线程等待后,一开始主线程和创建的另一个线程并行。直到执行到线程等待那一行代码。开始等待该指定线程执行完后再执行。
线程分离
补充知识线程状态
1.可结合态:
这种状态下的线程是能够被其他进程回收其资源或杀死的,这句话我的理解是:与其说它能够被其他进程回收或杀死,不如说它需要被其他进程回收或杀死;当它在被其他线程回收之前,它的存储器资源(如栈)是不会释放的;
这跟子进程很相似,如果不用父进程wait回收的话,就会变成僵尸进程同理,如果一个可结合态线程不用pthread_join回收,则会变成类似僵尸进程
2.分离态
这种状态下的线程是不能够被其他线程回收或杀死的;它的存储资源在它终止时由系统自动释放
为了避免存储器泄漏,每个可结合线程需要显示的调用pthread_join回收;>要么就将其变成分离态的线程。
线程分离的作用
默认情况下 新创建的线程处于可连接(Joinable)的状态可连接状态的线程退出后,需要对其执行连接操作,否则线程资源无法释放,从而造成资源泄漏。
如果其他线程并不关心线程的返回值,那么连接操作就会变成一种负担:你不需要它,但是你不去执行连接操作又会造成资源泄漏。 这时候你需要的东西只是:线程退出时,系统自动将线程相关的资源释放掉,无须等待连接。
可以是线程组内其他线程对目标线程进行分离, 也可以是线程自己执行pthread_detach函数。
线程的状态之中,可连接状态和已分离状态是冲突的,一个线程不能既是可连接的,又是已分离的。 因此 如果线程处于已分离的状态,其他线程尝试连接线程时,会返回EINVAL错误。
int pthread_detach(pthread_t thread)
成功返回0,错误返回错误码
将该函数放在线程内,指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),如果线程已经结束,又不释放资源就会变成僵尸线程。