linux应用开发基础知识(五)——线程

本文详细阐述了Linux系统中进程与线程的区别,包括资源分配、地址空间、创建成本、通信方式等,并介绍了线程的创建、结束方式以及线程等待和分离的概念,以及相关的函数如pthread_create、pthread_join和pthread_detach的使用。
摘要由CSDN通过智能技术生成

线程

线程与进程的区别

在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,错误返回错误码
将该函数放在线程内,指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),如果线程已经结束,又不释放资源就会变成僵尸线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值