LINUX【线程概念】

news2024/9/21 4:33:45

线程

    • 线程概念
    • 线程的优点
    • 线程的缺点
    • 线程异常
    • 线程用途
    • `linux`进程和线程
    • 线程控制
      • `pthread` 库
        • 创建线程
        • 线程等待
        • 线程退出
        • 线程分离
    • 线程互斥
      • 线程互斥相关概念
      • 线程互斥
      • 互斥量
      • 互斥量接口
        • 初始化互斥量
        • 销毁互斥量
        • 互斥量的加锁和解锁
      • 互斥量实现原理

线程概念

线程是进程中的一个执行流

一个进程可以有一到多个执行流

透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

linux中,进程和线程的数据结构都是pcb,线程是CPU调度的基本单位

进程的中的执行流共享同一个mm_struct页表

image-20230116191926046

虚拟地址通过页表转换到物理地址

首先,物理地址会被分成一个个的page,每个page为4kb,而且页表不只是包含虚拟地址,和物理地址,还包括是否命中,RWX权限,UK权限

是否命中:访问的数据是否在物理内存中

RWX权限:读写权限

U/K权限:当前是用户级页表还是内核级页表

image-20230131164646127

char* msg="hello world";
*msg='a';

上述代码编译没有问题,但是在执行时会报错,就是因为在运行,执行代码时,在页表转换时,是只读权限,而这句代码需要写的权限,所以运行报错

还有,页表并不是我们想象中直接从32/64位虚拟地址映射到32/64位物理地址,而是将虚拟地址分为3部分(10位/10位/12位),使用前10位通过一级页表(页目录)映射到二级页表,使用第二个10位映射到物理内存的某一个page的起始地址,最后12位正好对应每一个page的大小(4kb

image-20230131185852829

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程的缺点

  • 性能损失(如果计算密集型的线程数量比可用的处理器多,增加了同步和调度的开销,造成了性能损失)
  • 健壮性降低,线程不具有独立性,线程是不具有保护的
  • 缺乏访问控制
  • 编程难度提高

线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,进程内的所有线程也就随即退出

线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验

linux进程和线程

  • 进程是资源分配的基本单位
  • 线程调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据
    • 线程ID(LWP
    • 一组寄存器数据
    • errno
    • 信号屏蔽字
    • 调度优先级

进程的多个线程共享同一地址空间,因此代码段、数据段都是共享的,在一个线程中定义一个全局变量或一个函数,在其他线程也可以访问的,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式
  • 当前工作目录
  • 用户id和组id

线程控制

pthread

pthread库是一个动态库

image-20230203164124314

在内核中并没有线程,是使用LWP(轻量级进程)模拟的,而在用户层面需要创建进程、等待进程……,所以在在用户层和内核之间使用pthread库来实现。比如:用户创建线程,通过线程库,在内核中创建轻量级进程。

而且管理线程(先描述,再组织)是在pthread库中进行的

线程栈:每个线程有自己独立的栈,而主线程则直接使用进程地址空间中的栈区;

线程局部存储:同一个进程的多个线程对于全局变量是共享的,那么如果我们想让每个线程对于这个全局变量是独立的,那么每个线程的独立的全局变量就存储再线程局部存储中。

__thread int global=100;	//在全局变量前加上__thread,线性局部存储

image-20230203163808136

pthread_t类型是线程ID,但是和LWP(线程ID)不同

LWP(线程ID)是属于进程调度的范畴,内核中,线程是用轻量级进程模拟的,是操作系统调度的基本单位,LWP(线程ID)是用来唯一的标识线程的。

pthread_t类型的线程ID,本质上是一个地址,这个地址指向pthread动态库中的线程控制块,

创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg)
/创建线程
/thread: 线程ID(但不是LWP)
/attr: 设置线程ID,NULL为线程的默认属性
/start_routine: 该线程执行的函数
/arg: 传给函数的参数
/返回值:成功返回0,失败返回错误码

线程等待

int pthread_join(pthread_t thread, void **value_ptr);//线程退出后,需要进行线程等待,才能释放线程的资源
/thread :线程ID
/value_ptr :一个输出型参数,指向线程的返回码
/返回值:成功返回0,失败返回错误码

获取退出进程的退出码:
void *retval=nullptr;
pthread_join(tid1,&retval);
cout<<*((int*)retval)<<endl;		/退出码

代码

void *func1(void *args)
{
     while (true)
     {
           string name = (char *)args;
           cout << name << "正在运行" << endl;
           sleep(1);
     }
}

void *func2(void *args)
{
     while (true)
     {
           string name = (char *)args;
           cout << name << "正在运行" << endl;
           sleep(1);
     }
}

int main()
{
    pthread_t tid1, tid2;
     / 创建线程
     pthread_create(&tid1, nullptr, func1, (void *)"thread1");
     pthread_create(&tid2, nullptr, func2, (void *)"thread2");
     while (true)
     {
         cout << "主线程正在运行" << endl;
           sleep(1);
       }
     / 释放线程
     pthread_join(tid1, nullptr);
     pthread_join(tid2, nullptr);
 
    return 0;
 }

image-20230131190104760

查看进程

ps axj|head -1 && ps axj|grep mytest

image-20230131190430380

查看线程

ps -aL

image-20230131190330343

LWP:线程ID

查看线程自己的ID

pthread_t pthread_self(void);
void *func1(void *args)
{
    sleep(1);
    pthread_t id = pthread_self();
    cout << "tid1: " << id << endl;
}
void *func2(void *args)
{
    sleep(2);
    pthread_t id = pthread_self();
    cout << "tid2: " << id << endl;
}
int main()
{
    pthread_t tid1, tid2;

    pthread_create(&tid1, nullptr, func1, (void *)"thread1");
    pthread_create(&tid2, nullptr, func2, (void *)"thread2");
    sleep(3);
    pthread_t id = pthread_self();
    cout << "主线程: " << id << endl;
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);

    return 0;
}

image-20230131205029367

线程退出

**pthread_exit函数 **

void pthread_exit(void *value_ptr);/线程退出
/等同于return (void*)退出码

例子:
    int *p =new int(10);
    pthread_exit((void*)p);

pthread_cancel函数

int pthread_cancel(pthread_t thread);/取消一个执行中的线程
/thread 线程ID

return 退出码

return (void*)退出码;

例子:
    int *p =new int(10);
    return (void*)p;

注意:主线程结束,整个进程直接而结束

线程分离

int pthread_detach(pthread_t thread);/分离线程,不需要回收线程,操作系统直接释放线程
/可以由本线程分离,也可由其他线程分离
/一个线程如果分离了,就不能对该线程进行线程等待    

线程互斥

线程互斥相关概念

临界资源:多线程执行流共享的资源就叫做临界资源

临界区:每个线程内部,访问临界资源的代码,就叫做临界区

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

线程互斥

为什么需要线程互斥,看一个例子

一个关于抢票的代码:

int tickets = 10000;

/ 抢票
void *function(void *argc)
{
    while (true)
    {
        if (tickets > 0)
        {
            usleep(3000);
            cout << (char *)argc << " ticket:" << tickets << endl;
            tickets--;
        }
        else
        {
            cout << "failed ticket:" << tickets << endl;
            break;
        }
    }
}

int main()
{
    pthread_t tid1, tid2, tid3, tid4;
    / 创建线程
    pthread_create(&tid1, nullptr, function, (void *)"thread1");
    pthread_create(&tid2, nullptr, function, (void *)"thread2");
    pthread_create(&tid3, nullptr, function, (void *)"thread3");
    pthread_create(&tid4, nullptr, function, (void *)"thread4");
    / 主线程
    cout << tickets << endl;
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    pthread_join(tid4, nullptr);

    return 0;
}

image-20230206211517208

为什么会出现上面这种情况?

因为在判断完票数不为零和tickets--之间,发生了线程切换(因为多个线程并发执行),其他线程票数–了,等线程再切回来,再–,票数就可能变成负数

所以需要满足以下三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

为了解决这个问题,满足这三点,就需要加锁

互斥量

LINUX中把加的这把锁,叫做互斥量

加锁的本质就是将加锁的这段代码由并发执行变为串行执行

一般情况下,加锁的粒度越小越好,只给临界区加锁

互斥量接口

初始化互斥量

  • 静态分配

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
    
  • 动态分配

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrictattr);
    /mutex:要初始化的互斥量
    /attr:NULL
    

销毁互斥量

  • 使用 PTHREAD_MUTEX_INITIALIZER 初始化的互斥量不需要销毁

  • 不要销毁一个已经加锁的互斥量

  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

互斥量的加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);		/加锁
/返回值:成功返回0,失败返回错误号
  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);	/解锁
/返回值:成功返回0,失败返回错误号

改进抢票代码:

int tickets = 10000;

pthread_mutex_t mymutex;

/ 抢票
void *function(void *argc)
{
    while (true)
    {
        pthread_mutex_lock(&mymutex);
        if (tickets > 0)
        {
            usleep(3000);
            cout << (char *)argc << " ticket:" << tickets << endl;
            tickets--;
            pthread_mutex_unlock(&mymutex);
        }
        else
        {
            pthread_mutex_unlock(&mymutex);
            cout << "failed ticket:" << tickets << endl;
            break;
        }
    }
}

int main()
{
    pthread_mutex_init(&mymutex,nullptr);
    pthread_t tid1, tid2, tid3, tid4;
    / 创建线程
    pthread_create(&tid1, nullptr, function, (void *)"thread1");
    pthread_create(&tid2, nullptr, function, (void *)"thread2");
    pthread_create(&tid3, nullptr, function, (void *)"thread3");
    pthread_create(&tid4, nullptr, function, (void *)"thread4");
    / 主线程
    cout << tickets << endl;
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    pthread_join(tid4, nullptr);
    pthread_mutex_destroy(&mymutex);
    return 0;
}

互斥量实现原理

image-20230209210511641

如上图代码:

加锁:

  • 将0写入寄存器
  • 交换寄存器和内存中mutex的数据
  • 如果寄存器大于零,加锁成功,否则,阻塞等待

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ShonyDanza:如何利用Shodan实现自定义的安全研究与网络防护

关于ShonyDanza ShonyDanza是一款支持自定义且易于使用的安全工具&#xff0c;该工具基于Shodan实现其功能&#xff0c;并且可以利用Shodan的强大能力帮助研究人员实现安全研究、安全测试和安全防护等任务。 ShonyDanza的功能包括&#xff1a; 1、根据搜索条件获取IP 2、根据…

LabWindows CVI 2017开发笔记--串口调试软件实例

一、新建工程 打开LabWindows CVI软件&#xff0c;在桌面新建SerialDebug文件夹用来保存工程文件&#xff0c;在欢迎页点击New–>Project 或者在软件首页点击File–>New–>Project 将Project创建在新的Workspace中&#xff0c;设置完成后点击OK 新建一个用户GUI界…

内存溢出、内存泄露的概述及常见情形

内存溢出&#xff08;OutofMemoryError&#xff09; 简述 java doc 中对 Out Of Memory Error 的解释是&#xff0c;没有空闲内存&#xff0c;并且垃圾收集器也无法提供更多内存。 JVM 提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理&#xff0c;由于 GC&…

functional interface

更优雅 案例 要在TodoTaskRepository写find方法找到对应task列表&#xff0c;传入自定义的如何find 要传入两个参数&#xff0c;返回一个bool&#xff0c;在文档里找这样的functional interface java8官方文档-Package java.util.function BiPredicate是这样的 接口函数是t…

Numpy基础——人工智能基础

文章目录一、Numpy概述1.优势2.numpy历史3.Numpy的核心&#xff1a;多维数组4.numpy基础4.1 ndarray数组4.2 内存中的ndarray对象一、Numpy概述 1.优势 Numpy(Nummerical Python),补充了Python语言所欠缺的数值计算能力&#xff1b;Numpy是其它数据分析及机器学习库的底层库&…

GitLab CI/CD实现代码推送后自动maven打包发布

1、GitLab CI/CD介绍 CI(Continuous Intergration)&#xff1a;即持续集成&#xff0c;将代码的合并、部署、自动化测试都在一起&#xff0c;不断地执行这个过程&#xff0c;并对结果反馈。 CD(Continuous Delivery)&#xff1a;即持续交付&#xff0c;持续交付是一种软件工程方…

别具一格的婚礼,VR全景+婚礼的优势展现在哪里?

随着90后、95后逐渐步入结婚的主力军中&#xff0c;如何策划一场别具一格的婚礼是许多年轻人所头疼的&#xff0c;那么今年我们就可以玩点新潮的&#xff0c;VR婚礼或许是个不错的选择。 VR全景婚礼就是通过全景摄像机对婚礼进行记录&#xff0c;不但可以帮助新人捕捉婚礼的精彩…

对S参数的理解II

本篇文章特别感谢粉丝朋友“千年的呢喃”&#xff0c;他给我推荐了一本书&#xff0c;写的非常好 Micro Wave and RF Design&#xff0c;有需要的朋友自行下载。 之前关于S参数也写过几篇文章了&#xff0c;但一直以来都有一个历史遗漏问题没有解决&#xff0c;那就是&#xf…

ElasticSearch-学习笔记05【SpringDataElasticSearch】

Java后端-学习路线-笔记汇总表【黑马程序员】ElasticSearch-学习笔记01【ElasticSearch基本介绍】【day01】ElasticSearch-学习笔记02【ElasticSearch索引库维护】ElasticSearch-学习笔记03【ElasticSearch集群】ElasticSearch-学习笔记04【Java客户端操作索引库】【day02】Ela…

【IVIF的超分重建】

Multimodal super-resolution reconstruction of infrared and visible images via deep learning &#xff08;基于深度学习的红外和可见光图像多模态超分辨率重建&#xff09; 提出了一种基于编解码器结构的红外-可见光图像融合方法。图像融合任务被重新表述为保持红外-可见…

2023年3月AMA-CDGA/CDGP数据治理认证考试这些城市可以报名

目前2023年3月5日CDGA&CDGP开放报名的城市有&#xff1a;北京、上海、广州、深圳、杭州、重庆&#xff0c;西安&#xff0c;成都&#xff0c;长沙&#xff0c;济南&#xff0c;更多考场正在增加中… DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业…

Echarts 设置折线图线条样式(虚线+粗细+阴影)

第012个点击查看专栏目录Echarts折线图的lineStyle属性可以设置折线的颜色&#xff0c;粗细&#xff0c;类型&#xff0c;线段末端类型&#xff0c;阴影&#xff0c;透明度&#xff0c;偏移等属性。文章目录示例效果示例源代码&#xff08;共128行&#xff09;相关资料参考专栏…

【Java|多线程与高并发】 使用Thread 类创建线程的5种方法如何查看程序中的线程

文章目录前言线程创建1.继承Thread类重写run()方法如何查看程序中的线程?2.实现Runnable接口3.使用匿名内部类,继承Thread4.使用匿名内部类,实现Runnable5.⭐使用Lambda表达式,创建线程(重要)Thread 的常见构造方法总结前言 在这里主要补充说明一些问题,方便更好地理解下面的…

conda安装nodejs版本过低解决方法

conda命令直接安装nodejs时&#xff0c;可能会由于镜像源中nodejs版本过低导致没法安装高本版的nodejs&#xff0c;导致无法jupyterlab使用一些扩展插件。 解决方法如下&#xff1a;&#xff08;windows环境下直接按提示下载版本安装就行&#xff0c;此处只介绍linux环境的解决…

2023上半年软考中级系统集成项目管理工程师2月25日开班

系统集成项目管理工程师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职…

今天面试招了个28K的人,从腾讯出来的果然都有两把刷子···

公司前段时间缺人&#xff0c;也面了不少测试&#xff0c;前面一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资在20~30k&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。看简历很多都是4年工作经验&#xff0c;但面试中&#xff0c;不…

2023年上半年软考高项信息系统项目管理师2月25日开班

信息系统项目管理师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资…

每天一道大厂SQL题【Day09】充值日志SQL实战

每天一道大厂SQL题【Day09】充值日志SQL实战 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&#…

MMDetection(五)

目标检测工具包 MMDetection MMDetection 可以做什么 ➢ MMDetection 提供 400 余个性能优良的预训练模型&#xff0c;开箱即 用&#xff0c;几行 Python API 即可调用强大的检测能力 ➢ MMDetection 涵盖 60 余个目标检测算法&#xff0c;并提供方便易用的 工具&#xff0c;…

【Spring Cloud】如何修改Feign的日志记录级别

本期目录前言1. 介绍2. 方式一&#xff1a;配置文件1&#xff09;全局生效2&#xff09;局部生效3. 方式二&#xff1a;Java代码1&#xff09;全局配置2&#xff09;局部配置前言 本次示例代码的文件结构如下图所示。 1. 介绍 Feign 允许我们自定义配置&#xff0c;下面是 …