Linux多线程篇【1】——线程控制

什么是线程

线程就是程序里的一个执行路线。与windows不同,在Linux中并没有类似TCB(thread control block)这样的结构用来专门管理线程,而是和进程一样用task_struct来管理,因此可以说Linux中并不存在真正的线程,同时因为在Linux中进程与线程都是用tack_struct来管理的,即在Linux中的PCB小于等于整个线程组,因此我们把Linux中的进程称为轻量级进程。

重入函数

CPU会在运行队列中依次运行进程,假设我们我们创建了一个链表如下图:
在这里插入图片描述

此时有两个线程都可以访问到这个链表,他们分别执行如下操作,insert1想在head后插入节点3,insert2想在head后插入节点5,

void insert1(node* p)
{
	p->next = head->next;
	head->next = p;
}

void insert2(node* p)
{
	p->next = head->next;
	head->next = p;
}

但是在操作过程中发生了进程的替换,在insert1执行完第一条语句后,cpu开始执行insert2并在执行完后继续执行insert1,让我们来看看过程:
在这里插入图片描述
可以发现5节点不能在此链表中被查找到,即插入失败了。在这个例子中insert函数被重入了,而重入导致了节点的插入失败,因此我们可以说insert函数是不可重入函数。

volatile

在这里插入图片描述
我写了这样的代码,将2号信号的行为改为将flag设为1,以此来让main函数中的死循环结束,让我们看看操作结果,
在这里插入图片描述
看起来一切正常,我按了ctrl c后代码正常退出了,但实际上这个代码是有问题的,我们知道编译器为了改善程序执行效率会给程序加优化,而优化又有不同的级别,在gcc中有O0到O4五个级别的优化,让我们看看O3优化级别下程序的运行情况在这里插入图片描述
在这里插入图片描述
此时我们发现按传递2号信号不能让程序停止了,这是为什么呢?

在这里插入图片描述
在此处main函数和handler函数是两个执行流,编译器在main函数发现flag仅在循环中作为逻辑判断使用,于是就会把他直接保存到寄存器,这样cpu在执行程序时就不需要频繁地从内存中读取flag,加快了运行速度,但是也造成了一个问题,在handler中对flag进行了修改,修改的是内存中的flag,因此main函数无法得到flag的变化,导致循环不断进行。那么怎么解决这个问题呢?此时我们就需要用上volatile关键字了。
我们只需要在flag前加上volatile,程序就又可以正常退出了

在这里插入图片描述
在这里插入图片描述
此时我们差不多就知道volatile的作用了:告诉编译器不要对该变量做任何优化,每次都必须读取内存,不要读取寄存器中的数据。(保持内存的可见性)

我们一般将一个进程内的所有线程称为一个线程组,而组id就是pid即进程id
线程的优点
1.创建一个新线程的代价比创建一个进程小得多
2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3.线程占用的资源更少
4.计算密集型应用为了能在多处理器系统上运行,将计算分解到多个线程中实现()
5.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
计算密集型:加密,大数据运算等,主要使用的CPU资源
I/O密集型:网络下载,云盘,ssh,在线直播等,主要使用内存外设的IO资源
CPU+I/O密集型的应用:网络游戏

用途:
合理使用多线程,能提高机算密集型程序的执行效率
合理的使用多线程,能提高IO密集型程序的用户体验(如我们一边写代码一边下载开发工具,就是多线程运行的一种表现)

线程创建

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start routine) (void *), void *arg);
  • thread:返回线程ID
  • attr:设置线程的属性,attr为NULL表示默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数
  • 返回值:成功返回0,失败返回错误码

线程等待

1.代码跑完结果对
2.结果不对
3.代码异常了
pthread_join不需要处理,线程异常不需要进程处理

int pthread_join(pthread_t thread, void **retval);
//输出型参数,用来获取新线程退出的时候,函数的返回值

线程终止的方案:

1.函数中的return(a.main函数退出的时候代表(主线程and进程退出) b. 其它线程函数return,只代表当前线程退出)
2.新线程通过pthread_exit终止自己,exit函数用来终止进程,如果只想终止当前线程,不要调用exit
void pthread_exit(void *retval);
3.取消目标线程
int pthread_cancel(pthread_t thread);

pthread_t pthread_self();
自己的线程id
如果不想等待
线程分离,分离之后的线程不需要被join,运行完毕后,会自动释放Z pcb
如何分离
int pthread_detach(pthread_t thread);
一个线程被设置为分离后就不能被join了。一般join的情况是主线程不退出,新线程处理完业务后退出

进程ID

我们查看到的线程ID是pthread库的线程id,不是Linux内核中的LWP,pthread库的线程id是一个内存地址。
在这里插入图片描述

让我们回顾一下Linux中线程创建的过程,我们首先创建主线程,那么我们会获得主线程的PCB,PCB中的虚拟内存里会给主线程分配一个栈,然后我们又会创建一个新的线程,那么这块线程必然也需要一个属于自己的栈,那么这个栈应该被放在那里呢?这些栈被放到了线程库中,我们得到线程id(即线程的虚拟地址)后即可以通过页表快速映射到线程的属性和数据结构在库中存储的位置。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JayceSun449

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值