Linux线程补充1

十、多线程中线程间的"独立"

​ 1.线程在代码段通过执行不同的函数,实现代码段的独立;

​ 2.新线程通过在共享区划分不同的管理属性和不同的栈空间,实现栈的独立,而主线程使用的是栈空间;

​ 3.线程通过获取到不同的堆空间地址,实现堆的独立;

​ 总结:线程是通过对共享资源的划分来实现资源的独立的,但是只要将其他线程的资源地址添加到当前线程当前线程也是可以访问到的;下图main thread访问了thread-2的局部变量;

在这里插入图片描述

const int num = 10;
void *threadRoutine(void *args)
{
    const char *s = static_cast<const char *>(args);
    int cnt = 1;
    while (cnt)
    {
        cnt++;
        cout << "thread name: " << s << " pid: " << getpid() << endl;
        if (cnt == 10)
            break;
    }
    return nullptr;
}
string str;

int main()
{
    vector<pthread_t> vtid;
    for (int i = 0; i < num; i++)
    {
        pthread_t tid;
        str += "thread";
        str += " : ";
        str += to_string(i);
        pthread_create(&tid, nullptr, threadRoutine, (void *)str.c_str());
        vtid.push_back(tid);
    }
    for (int i = 0; i < vtid.size(); i++)
    {
        pthread_join(vtid[i], nullptr);
    }
    return 0;
}

十一、线程局部存储

11.1__thread

​ __thread是编译器提供的一个编译选项,让被修饰变量在线程的局部存储里都开辟一份;,本质上就不是同一个变量了;需要注意的是**__thread只能修饰内置类型**;通常是用来存储线程的属性tid、pid之类的;

__thread int val_;
//定义一个全局变量被__thread修饰,全局变量对于每一个线程都是各自私有一份;
//此技术叫做线程的局部存储;

11.2局部存储与独立栈的区别

​ 独立栈主要是用来保证调用链的安全,里面放的是临时变量;

​ 而局部存储类似全局数据区,存放的数据是随线程的整个生命周期的;

十二、线程分离

​ 类似进程非阻塞等待;

​ 默认创建的新线程是joinable需要被等待的,不进行等待无法释放资源,会造成内存泄露;主线程不关心新线程的退出情况,那是由进程来关心的,主线程关心的是函数执行完之后的返回值;如果主线程不想知道新线程的返回值,就不需要阻塞等待,此时要进行线程分离;

​ 线程分离即新线程自己将内核管理的LWP结构和库管理的tcb结构释放;

注意,1.线程分离之后是不可以被join的;2.必须保证主线程是最后一个退出的,否则会导致整个进程的退出

​ 一个线程是否被分离是需要被记录下来的,所以一个线程被分离本质就是将本线程的tcb里的线程可标志位设置为1;

12.1线程分离接口

#include <pthread.h>
int pthread_detach(pthread_t thread);
//即不需要主线程阻塞等待,执行完之后自己释放;
//可以由主线程进行分离
pthread_t tid;
pthread_detach(tid);
//新线程自己释放自己
pthread_detach(pthread_self());

十三、线程的同步与互斥

​ 多线程并发访问共享资源的时候是有数据不一致问题的;

互斥:任何时候访问资源只有允许有一个执行流;

同步:按照一定的顺序性获取资源;

原子性:在代码上表现为只有一个汇编指令,只有执行前和执行后两种状态;

​ 对一个全局变量进行多线程并发–/++操作是不安全的,因为如–操作需要进行三步操作:1.将内存中的数据读取到CPU当中;2.在CPU内部进行–操作;3.将计算结果写回内存;

​ 即CPU执行是按照汇编走的,应用层的一行代码会被转换成多条汇编指令,所以一行代码不具有原子性;

​ CPU的寄存器保存着线程的上下文,当线程被换走的时候,要从寄存器中拿走上下文,换回是要将上下文恢复到寄存器中,对于–操作最后是要将寄存器中的内容写入到内存当中的,对于共享资源多线程并发访问时,当前线程因为时间片结束导致数据没有被修改并将数据保存到上下文中,其他线程对数据成功进行了修改,当线程恢复上下文是就会用自己的上下文修改数据,但是此时的数据已经不是刚从内存读取到的数据了,这时修改内存数据就会有问题;

​ 总结:多线程并发访问时会使得数据同时被修改,造成数据不一致问题;

​ 解决方式:任何时候共享数据只允许有一个执行流,即互斥,这样某个线程执行的时候,其他线程就不会修改共享数据;使用锁的方式实现互斥;

​ 互斥场景下,1.由于线程对于锁的竞争能力不一样会导致其他线程饥饿问题;2.由于大量线程是被阻塞的,一旦资源就绪,默认所有线程就会都从阻塞态变成运行态竞争锁,但是最多只有一个线程可以访问,其他线程是无效的,被伪唤醒了,而且会导致被唤醒的线程CPU都要执行一次检测逻辑,这就叫做惊群问题;

​ 解决方式:1.所有的线程必须排队;2.释放完锁的线程不能立马申请锁,排到队尾;

​ 这样可以让线程获取锁有一定的顺序,至少保证了每个线程都有机会获得锁,是相对公平的;

十四、互斥锁

​ 锁是一种共享资源是通过保证申请和释放锁的原子性来保证访问锁是安全的;

​ 先定义一把锁,然后初始化和销毁,期间对临界区加锁和解锁;

14.1锁的初始化和释放

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//参数是锁的地址;
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//第一个参数是锁的地址;第二个参数是锁的属性一般为nullptr;
pthread_mutex_t/*是库里面提供的一种数据类型*/ mutex = PTHREAD_MUTEX_INITIALIZER;
//使用全局或者静态的方式进行初始化之后是不需要destroy的;

14.2加锁和解锁

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁,申请成功可以访问,失败阻塞;
int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞加锁,解决死锁问题;
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁;底层设计是为了解决死锁问题;

14.3加锁后的缺陷

​ 加锁会导致加锁区域进行串行访问,本质上是以时间换取安全;所以应该尽量保证临界区即加锁的区域的代码要少;好处是执行时间少,串行比率降低,其他线程等待的时间减少;

​ 加锁产生饥饿问题;

14.4加锁的细节

​ 1.申请锁成功才可以继续往后执行;不成功就会阻塞等待资源就绪;

​ 2.加锁之后必须解锁,否则会导致锁资源不就绪引发其他线程一直阻塞的问题,所以要保证锁一定可以被解锁,保证不会因为进行跳转不能执行到解锁;

​ 3.不同线程对锁的竞争能力不同;不同线程对锁的访问是并发的;为了保证竞争公平要进行同步;

​ 4.如果线程长时间得不到锁就会导致饥饿问题,即纯互斥环境,锁资源分配不够合理,导致其他进程的饥饿问题;

​ 5.加锁后的线程也是可以被切换的,但是此线程还是持有锁,没有释放锁,其他线程是无法访问临界区的;因为其他线程只关心当前线程是持有锁还是释放锁,即是此线程对于其他线程是原子的;

14.5互斥锁的底层实现

​ 大部分芯片在设计时一定要内置汇编指令集才能执行各种复杂的逻辑;

​ 互斥锁底层使用了swap这样的指令,作用是将内存和寄存器中的值进行交换,一条指令所以是原子的,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期,因为多处理器也只有一套总线和内存连接,CPU访问内存是要经过硬件仲裁器决定有哪一个CPU去访问内存;即多处理器CPU访存还是串行的,只是计算是并发的;

​ 伪代码:

​ 每一个线程的上下文都是私有的,即寄存器中的数据都是属于线程的;线程的结构中上下文是软件上下文,在寄存器中是硬件上下文;锁本质上就是一个数字1,只有一个,swap就可以保证锁申请成功,其他指令都是后续动作

​ 交换的本质就是将内存中的数据(共享数据)交换到寄存器中,也就是线程的硬件上下文(私有数据)中;如果当前线程继续是申请锁就会导致唯一的1也变成0,即没人能够申请锁了,死锁了;

lock:
	//8086的ax寄存器是16位的,后来eax是32位,e是expand扩展的意思,分为了ah和al各16位
	movb $0,%al//将寄存器al的值设为0
	xchgb al, mutex//将寄存器的值交换到变量mutex里,原本mutex是1变为了0,这就是申请锁的过程
	if(al寄存器的内容 > 0)//申请成功此时寄存器内部就是1,否则就是失败挂起等待
    {
		return 0;
	)
	else
		挂起等待;
	goto lock;
unlock:
	movb $1, mutex//将内存中的mutex变量置为1,这样的实现就可以使得其他线程来释放锁,而不是必须当前线程来释放锁;这样就可以实现两个线程的同步;如果使用的是swap就必须保证是申请锁成功的线程来释放锁;为解决死锁问题提供了方案;
	唤醒等待Mutex的线程;
	return 0;

14.6锁的应用——封装

#include <pthread.h>

class mutex
{
public:
    mutex(pthread_mutex_t *lock) : lock_(lock) {}
    void lock()
    {
        pthread_mutex_lock(lock_);
    }
    void unlock()
    {
        pthread_mutex_unlock(lock_);
    }
    ~mutex() {}

private:
    pthread_mutex_t *lock_;
};
class lockguard
{
public:
    lockguard(pthread_mutex_t *lock) : mutex_(lock)
    {
        mutex_.lock();
    }
    ~lockguard()
    {
        mutex_.unlock();
    }

private:
    mutex mutex_;
};
//可以使用代码块{}来划分对象的的生命周期

14.7死锁

​ 同一把锁申请两次就会形成死锁,原因是底层锁其实就是1,申请了两次使得整个过程中1丢失了,所以死锁了;

14.7.1概念

​ 死锁是指在一组执行流中的各个执行流均占有不会释放的资源,但因互相申请被其他执行流所占用不会释放的资源而处于的一种永久等待状态。

14.7.2死锁四个必要条件

​ 互斥条件:一个资源每次只能被一个执行流使用;

​ 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放; 默认使用的lock接口就是请求与保持的;

​ 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺;

​ 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系;

​ 缺一不可;

14.7.3解决死锁问题

​ a.破坏死锁的必要条件,如:

​ 1.不使用互斥;2.使用pthread_mutex_trylock(pthread_mutex_t *mutex),申请失败返回然后将自己的锁释放;3.由于释放锁的底层是直接赋1,所以可以由其他线程释放当前线程的锁的;4.每一个线程申请锁的顺序要一致,否则容易形成环路申请问题;

​ b.加锁顺序一致;

​ c.避免锁未释放的场景;

​ d.资源一次性分配;

​ 相关算法:1.死锁检测算法;2.银行家算法;

十五、线程安全问题

​ 线程安全:多个线程并发同一段代码时,不会出现不同的结果。

常见的线程不安全的情况:

​ 1.不保护共享变量的函数;

​ 2.函数状态随着被调用,状态发生变化的函数;

​ 3.返回指向静态变量指针的函数;

​ 4.调用线程不安全函数的函数;

​ 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入

​ 一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则是不可重入函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/482248.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机二级大题

题目来源&#xff1a;计算机二级Python半个月抱佛脚大法&#xff08;内呈上真题版&#xff09; - 知乎 1.大题1 注意csv文件读取的处理 ls[] for line in f: ls.append(line.strip(\n).split(,)) 2. 大题2 第一问&#xff1a; #计算有效票张数 fopen("vote.txt",…

微服务鉴权的几种实现方案

1.Token 1.1 Token透传&#xff08;不推荐&#xff09; 刚开始接触微服务时网上给的方案大都数是通过透传Token做鉴权&#xff0c;但我认为这种方式不是很妥当。接着往下看&#xff1a; 这种方式通过透传Token使得各微服务都能获取到当前登录人信息&#xff0c;在代码编写上确…

SCI一区 | Matlab实现WOA-TCN-BiGRU-Attention鲸鱼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现WOA-TCN-BiGRU-Attention鲸鱼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现WOA-TCN-BiGRU-Attention鲸鱼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程序…

面试算法-78-两两交换链表中的节点

题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff…

NLP 笔记:LDA(训练篇)

1 前言&#xff1a;吉布斯采样 吉布斯采样的基本思想是&#xff0c;通过迭代的方式&#xff0c;逐个维度地更新所有变量的状态 1.1 举例 收拾东西 假设我们现在有一个很乱的屋子&#xff0c;我们不知道东西应该放在哪里&#xff08;绝对位置&#xff09;&#xff0c;但知道哪…

汉字之美,拼音之韵

title: 汉字之美&#xff0c;拼音之韵 date: 2024/3/23 18:41:56 updated: 2024/3/23 18:41:56 tags: 汉字拼音文化语言美学传承中文 1. 汉字之美 汉字作为中文的书写形式&#xff0c;承载着丰富的文化内涵。每一个汉字都蕴含着历史、传统和智慧&#xff0c;是中华文明的瑰宝…

关于Java发邮件提醒写周报实现(一)环境搭建

背景 由于公司每周都要写周报&#xff0c;而日常工作很忙&#xff0c;所以很容易忘记这件事件&#xff0c;因此开发一个写周报提醒的机器人&#xff0c;进行特定时间提醒是时候写周报了。 有一个大前提&#xff0c;本技术实现&#xff0c;本着不开通任何收费服务的态度去考察使…

JetBrains CLion 2022 for Windows:C++开发者的强大助手,引领编程新风尚

在数字化浪潮席卷全球的今天&#xff0c;编程语言的多样性和复杂性日益凸显。而在众多编程语言中&#xff0c;C以其独特的优势和广泛的应用领域&#xff0c;成为众多开发者的首选。JetBrains CLion 2022&#xff0c;作为一款专为C开发者打造的集成开发环境&#xff08;IDE&…

深度学习(二)安装tensorflow深度学习框架

0.前言 速度更新新的一期&#xff0c;快夸奖我。前情提要这是我在window10系统下完成的操作&#xff0c;并不是ubuntu&#xff0c;所以有相应的区别。 1.安装tensorflow和d2l 这里默认大家已经安装好了anconda或者miniconda并且以及创建了虚拟环境。 conda create -n huahuaji…

Cesium安装部署运行

目录 1.简介 2.Cesium项目下载 3.Cesium项目运行 4.cesium运行 1.简介 Cesium是国外一个基于JavaScript编写的使用WebGL的地图引擎。Cesium支持3D,2D,2.5D形式的地图展示&#xff0c;可以自行绘制图形&#xff0c;高亮区域&#xff0c;并提供良好的触摸支持&#xff0c;且支…

(一)基于IDEA的JAVA基础6

赋值运算符 int a10&#xff1b;是把10赋值给了变量a&#xff0c; 那这里有两组数值: int num11&#xff1b; int num22&#xff1b; 想把两个数值互关该怎么办呢&#xff0c; 理想状态我们直接num1num2&#xff1b;num2num1&#xff1b;看一下结果: 全变成了2&#xff0…

【计算机网络】常见面试题汇总

文章目录 1.计算机网络基础1.1网络分层模型/OSI七层模型是什么&#xff1f;1.2TCP/IP四层模型是什么&#xff1f;每一层的作用&#xff1f;1.2.1TCP四层模型&#xff1f;1.2.2为什么网络要分层&#xff1f; 1.2常见网络协议1.2.1应用层常见的协议1.2.2网络层常见的协议 2.HTTP2…

如何查看局域网内所有的ip和对应的mac地址

1、windows下查看 方法一、 按快捷键“winr”打开运行界面&#xff0c;输入“CMD”回车: 输入以下命令&#xff1a; for /L %i IN (1,1,254) DO ping -w 1 -n 1 192.168.0.%i 其中 192.168.0.%i 部分要使用要查询的网段&#xff0c;比如 192.168.1.%i 192.168.137.%i 172.16.2…

git 上传文件夹至远端仓库的方法

上传的远端git可以是gitlab、github、gitee、gitblit或者gitCode等等 以下以GitHub为例说明&#xff1a; 1、登录GitHub网站&#xff08;账户/密码&#xff09; 2、创建一个新的空白项目&#xff08;或者已有的项目&#xff09;hello-world 分支是master &#xff0c;这里默认即…

【c++初阶】C++入门(下)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

Spark与flink计算引擎工作原理

Spark是大批量分布式计算引擎框架&#xff0c;scale语言开发的&#xff0c;核心技术是弹性分布式数据集&#xff08;RDD&#xff09;可以快速在内存中对数据集进行多次迭代&#xff0c;支持复杂的数据挖掘算法及图形计算算法&#xff0c;spark与Hadoop区别主要是spark多个作业之…

试题E(求阶乘)

解题思路&#xff1a; 写不出来&#xff0c;看的题解。要想凑个10&#xff0c;就必须要有一个2和5&#xff0c;但是明显在一个阶乘里&#xff0c;因子为2的数量一定多余5的数量&#xff0c;所以计算5的数量。 解题代码&#xff1a; import java.util.Scanner; ​ public clas…

题目:异或森林(蓝桥OJ 3400)

问题描述&#xff1a; 解题思路&#xff1a; 一个数也可以看作是一段区间&#xff0c;当该区间的异或和为完全平方数时则符合题意。 我们需要注意枚举的完全平方的上限。 异或前缀和减小时间复杂度。 题解&#xff1a; #include <bits/stdc.h> using namespace std; usi…

SpringBoot整合MyBatisPlus实现增删改查

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉🍎个人主页:Leo的博客 💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:SpringBoot整合MyBatisPlus实现增删改查 📚个人知识库: Leo知识库,欢迎大家…

Aztec的客户端证明

1. 引言 隐私保护 zk-rollup 的证明生成与通用 zk-rollup 的证明生成有很大不同。原因是给定交易中存在特定数据&#xff08;由私有函数处理&#xff09;&#xff0c;我们希望保持完全私有。在本文中&#xff0c;我们探讨了用于证明私有函数正确执行的客户端证明生成&#xff…
最新文章