[Linux]线程概念与控制

目录

一、线程概念

1.什么是线程

2.线程的轻量化

3.LWP字段

4.局部性原理

5.线程的优缺点

6.进程VS线程

二、线程的控制

1.线程创建

2.获取线程id

3.线程退出与等待

4.创建轻量级进程

三、线程的管理

1.pthread库管理线程

2.线程局部存储

四、C++线程库

1.构造函数

 2.成员函数


一、线程概念

1.什么是线程

        线程是比进程更加轻量化的,在进程内部分出来的执行的一种执行流。线程才是CPU调度的基本单位,进程是承担系统资源的基本实体。

2.线程的轻量化

        为什么说线程轻量化呢?因为创建线程的时候只需要创建一个PCB参与资源的分配即可,这里的资源指的是进程地址空间,也就是说线程也是在进程的地址空间中运行的。所以不需要创建进程地址空间、页表并建立映射,以及文件描述符、信号处理方式、当工作目录等等数据信息都是共享的,所以说线程是一个非常轻量化的执行流。当然每种系统对于进程的创建是不一样的,上述是Linux方案,而windows方案中设计了一种单独的TCB体系。

        那么很多内容都是共享的,所以不像父子进程那样修改一个变量会发生写时拷贝,对于多线程环境下来说,同时修改一个进程不会发生写时拷贝,而是修改的同一个物理内存位置的变量,因为他们是在共同完成一个任务。

3.LWP字段

        其实Linux中是不存在真正的线程的,而是在进程的数据结构体系上模拟出来了线程,每个线程被创建之后会参与资源的分配(进程地址空间),每个线程执行一部分进程的代码,共同完成一个进程任务。CPU在调度的时候是按一个一个PCB调度的,进程还是线程都有自己的PCB,所以CPU不需要区分是进程还是线程,他们的PCB都是地位对等的。

        创建多线程的本质:划分页表地址-->划分进程地址空间。

        同一个进程中多个线程执行流的pid是相同的,因为是同一个进程。可以使用ps -aL查看多线程,会出现新的名词LWP(light weight process)用来区别不同的线程,主线程的PID和LWP是一样的CPU在真实调度的时候其实看的是LWP。

4.局部性原理

        线程不仅仅是创建上更加简单,更加轻量化,在调度方面也比较简单。线程在切换的时候,只需要修改少量的CPU中的寄存器即可,页表,地址空间等上下文是不需要切换的。而进程切换时是全部都需要替换。

        局部性原理就是例如代码在执行第500行的时候,大概率下一次执行会执行501行,所以对于一个大型程序可以一边运行一边加载。CPU中有一个大的cache缓存,也是同理,不会一行一行的去内存读取代码和数据,而是一次读取一部分到cache缓存中,并且把常用的数据也放进去,这样CPU很大程度上只会在cache中取数据,提高了效率。局部性原理可以说为预加载机制提供了理论依据。

        所以说线程在切换的时候,很大几率上不会使cache中的大部分数据失效,只会去替换一小部分数据以及寄存器。所以说线程的调度切换上也很简单。

        对于调度方面,每个进程都有一个时间片,时间片也是资源,那么划分到线程上,是多个线程平均瓜分一个时间片的,保证了在一个时间片内可以使得多个线程都能被调度。

5.线程的优缺点

优点

  • 轻量化:在创建、调度、释放等方面体现。
  • 可以多处理器并行处理任务:对于多核处理器只运行一个或几个进程的时候,没有达到所有的处理器都在工作,所以无法发挥出多核处理器的性能,一个进程变成了多个线程,也就是多个执行流的话,可以使所有核心都参与工作发挥性能。
  • 对于计算密集型和I/O密集型应用会提高效率:可以减少等待时间,让一个进程去执行计算和I/O操作,主线程和其他进程继续向下执行,等到需要计算数据和I/O数据的时候,该线程已经获取到了。

缺点

  • 性能损失:如果说线程数量比可用的处理器多的话,会增加额外的同步与调度开销(上下文切换,调度器负载增加等问题),因为多个线程竞争有限的处理器资源的时候,可能会导致缓存失效率增加,以及多个线程的管理也是问题,会占用过多的系统资源。
  • 缺乏访问控制:多线程可以同时修改一个变量、函数等各种资源,所以需要配合加锁来控制。
  • 健壮性降低:任何一个线程出错崩掉的话,真个进程可能会终止。
  • 变成难度比较高

6.进程VS线程

        下面是从资源分配、调度与执行、并发性与独立性以及销毁等方面阐述的。

        进程是资源分配的基本单位,每个进程有自己独立的地址空间、包括代码段、数据段以及堆栈区域等。这意味着进程之间的数据是相互隔离的,一个进程无法直接访问另一个进程的数据,无法直接通信,拥有较高的独立性。还拥有自己独立的文件描述符表、进程PID等资源。但是多进程的调度上就会费力一些,需要全部替换。销毁的时候成本也比较高,需要将整个进程体系的数据结构全部释放。

        线程是进程中的执行单元,他共享所属进程的资源。多个线程中可以相互访问相同的代码段、数据段以及打开的文件等各种资源。独立性不是很好,但是对于调度方面需要替换的上下文数据很少,所以调度很简单。在并发性上,多个线程高效的协同工作,共享数据可以提高执行的效率。在销毁方面也比较简单,只需要销毁创建PCB等少量数据结构就可以。

二、线程的控制

1.线程创建

头文件   <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

                                                                        void *(*start_routine)(void*), void* arg);

        pthread_t是一个unsigned long类型,thread是一个输出型参数用来接收线程的id;attr是用来设置线程的属性,一般设置为nullptr即可;start_routine是一个函数指针,传递一个函数地址,用来告诉线程需要执行的哪个部分的代码,arg则是传递给start_routine的参数,没有的话,设置为nullptr。

        在使用该函数的时候,编译的时候可能会报错,因为Linux中没有实际的线程,只有轻量级进程,所以Linux内核只会提高轻量级进程创建的系统调用接口,没有线程的接口,所以上述的接口属于第三方库实现的。        

        pthread属于第三方库,属于用户与系统交互的一个中间软件层,该线程库为用户层提供线程的各种接口,当用户进程创建操作的时候,在线程库内部会将创建的线程,通过调用创建轻量级进程的系统调用接口,将线程转化为操作系统认识的轻量级进程。对于第三方库的调用,即使说将头文件以及库文件放入了指定的环境位置,也需要在编译的时候指定写出使用哪一个库才可以,所以不写的话编译的时候可能会报错。

        C++也有多线程库,但是内部也是对pthread库的一个封装。

2.获取线程id

pthread_t pthread_self(void);

        绝大多数情况用于线程函数内部自己获取自己的线程id 。

3.线程退出与等待

        线程的退出分为三种,一种是跑完代码后自己退出线程函数或者说特定的判断条件后直接return返回,另一种则是使用线程退出函数,pthread_exit和exit不一样,exit会使整个进程退出,而这个接口只会让当前线程退出。retval是退出的时候,返回的参数。最后一种是使用了线程取消函数pthread_cancel,会发送SIGCANCEL信号异常终止该线程。

void pthread_exit(void* retval);

int pthread_cancel(pthread_t thread);  

        对于退出的线程和子进程一样,也需要进行线程等待,用于获取线程的执行情况,返回值以及对线程资源进行回收操作,防止出现内存泄露。

int pthread_join(pthread_t thread, void** retval); 

        thread是传递的需要等待的线程id,retval则是输出型参数,用于接收线程正常退出的返回值,如果是异常退出的话,整个进程都会终止,所以接收一个线程的返回值已经没有意义了。该等待方式是阻塞式等待。

        阻塞等待和接收返回值的操作,对于一些计算、I/O场景需要确保线程执行完毕后,获取到线程计算的值或者I/O读取的值之后,才可以进行的场景很有用。因为可能下面的代码需要用到线程执行的结果,但是线程计算完,主线程就往下运行的话,对于使用到线程计算数据的操作可能是未定义的,所以阻塞式等待可以确保上述场景不会出错。

        线程模式默认式joinable的也就是可以被等待的,但是我们可以手动设置为分离状态,也就是不需要进行等待该线程,适用于不需要获取线程返回值的场景,分离之后,主线程就不会因为等待一些对于自己执行没有用处的线程而阻塞住了。设置为分离状态也就和进程忽略SIGCHLD信号一样,运行结束后会直接交给操作系统领养,不会造成内存泄露问题。

int pthread_detach(pthread_t pthread);

4.创建轻量级进程

int clone(int (*fn)(void*), void* stack, int flags, void* arg, ...);

        第一个参数是轻量级进程执行的方法,stack是用户传入栈空间地址,flags表示创建子进程还是创建轻量级进程,所以说fork的底层也是使用的clone接口。

        在进程替换的时候,最好是在线程中先创建一个子进程,之后再进行替换,因为程序替换是进程级别的,一个线程执行程序替换,替换的也是整个进程。

三、线程的管理

1.pthread库管理线程

        虽然说操作系统中没有线程的概念,但是用户层面是把轻量级进程作为线程来处理的,所以就需要有人帮我们进行两者之间的转化,也就是pthread原生第三方库,该线程库会做上述的操作,同时他也会创建很多个线程,所以需要将多个线程使用先描述再组织的操作进行管理多个线程,类似于windows的tcb结构体。同时线程库内部也一定有调用创建轻量级进程的接口、lwp字段的映射等内容。

        对于栈空间来说如何做到每个线程都独立的呢?对于主线程使用的是进程地址空间中的栈区域,而对于其他线程来说使用的是由线程库提供的栈空间。C语言的文件FILE结构体中都有一个对应的文件缓冲区,是由C库自己维护的,所以说库是有维护一块地址空间的能力的。线程库提供栈区的方式是在进程地址空间中的堆区malloc一块空间作为其他线程的栈区,让其他线程的栈区指针指向该malloc的区域。

        因为pthread是一个动态的库,所以说要程序运行的时候,要现在加载到内存,然后映射到进程地址空间的共享区内部。pthread库内部会把一个个线程用结构体管理起来,内部由线程栈指针指向自己堆区内部的栈空间还有线程局部存储、LWP等字段。对于我们创建的线程获取到的线程id,其实就是该线程属性结构体在线程库中的地址。 

2.线程局部存储

        对于全局变量在前面加一个__thread编译选项的话,在创建多线程时,在每个线程的局部存储里都会开辟一个空间存储该变量的值(只能修饰内置变量),对于局部存储的变量是每一个线程都有一份,属于线程的私有变量,修改不会影响其他线程。

四、C++线程库

        C++是将线程封装成了一个std::thread类,创建一个线程就相当于创建了一个类对象,对线程的所有操作都转化为了对于类对象的操作。

1.构造函数

  • 默认构造函数时std::thread():表示创建一个空的线程对象,不关联任何的实际线程。
  • 关联函数的构造函数:std::thread(Fn&& fn, Args&&...  args),这个是最常用的构造函数,用于创建一个线程并关联一个可调用对象(函数、函数对象、lambda表达式等),Fn是可调用对象的类型,Args表示可调用对象的参数类型。对于C++11的线程库,传递的线程函数对于返回值以及参数是没有要求的。
    // 关联普通函数
    void print_hello()
    {
        std::cout << "Hello, world!" << std::endl;
    }
    std::thread t1(print_hello);
    
    // 关联函数对象
    class Printer
    {
    public:
        void operator()() const
        {
            std::cout << "Function object printing." << std::endl;
        }
    };
    std::thread t2(Printer());
    
    // 关联lambda表达式
    std::thread t3([]()
                   { std::cout << "Lambda expression printing." << std::endl; });
    
    // 线程的参数传递
    void print_number(int num1, int num2) 
    {
        std::cout << "The number1 is: " << num1 << "number2 is: " << num2 << std::endl;
    }
    std::thread t4(print_number, 10, 20);
    

 2.成员函数

  • join()函数:用于线程等待
  • detach()函数:用于线程分离
  • joinable()函数:用于检查对象是否可以被join或detach,如果说线程对象关联了一个实际运行的线程,而并非空的线程对象的话,会返回true,否则返回false。

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

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

相关文章

cmake--库链接--RPATH--RUNPATH

RPATH--RUNPATH RPATH 是一种嵌入到二进制文件(可执行文件/库文件)中的路径信息&#xff0c;也就是存在于可执行文件或者库文件中的&#xff0c; 用RPATH(旧)或者RUNPATH(新)参数记录的路径信息&#xff0c; 指示动态链接器在运行时查找共享库的位置。 查看二进制文件的RPATH或…

Chapter 4.4:Adding shortcut connections

4 Implementing a GPT model from Scratch To Generate Text 4.4 Adding shortcut connections 接下来&#xff0c;让我们讨论 shortcut connections&#xff08;快捷连接&#xff09;背后的概念&#xff0c;也称为 skip connections&#xff08;跳跃连接&#xff09;或 resid…

Web渗透测试之XSS跨站脚本 原理 出现的原因 出现的位置 测试的方法 危害 防御手段 面试题 一篇文章给你说的明明白白

目录 XSS介绍的原理和说明 Cross Site Scripting 钓鱼 XSS攻击原理 XSS漏洞出现的原因&#xff1a; XSS产生的原因分析 XSS出现位置&#xff1a; XSS测试方法 XSS的危害 防御手段&#xff1a; 其它防御 面试题: 备注&#xff1a; XSS介绍的原理和说明 嵌入在客户…

热门数据手套对比,应用方向有何不同?

AI与人形机器人是目前市场中大热的两个新行业。在人形机器人或拟人仿真机器人制造与开发中动作捕捉技术的融入是必不可少的&#xff0c;通过将动捕数据与先进的AI大数据训练技术相结合&#xff0c;不仅能够省去枯燥乏味的动作编程过程大幅减少训练时间&#xff0c;还可以使训练…

dbt Semantic Layer 详细教程-1 :总体概述

dbt 语义模型提供语言描述方式快速定义业务指标。本文介绍语义模型作用和意义&#xff0c;以及语义模型的组成部分&#xff0c;后面会继续介绍如何定义语义模型&#xff0c;基于语义模型定义指标&#xff0c;如何通过MetricFlow&#xff08;语义层框架&#xff09;能够构建用于…

JAVA:探讨 CopyOnWriteArrayList 的详细指南

1、简述 在 Java 的并发编程中&#xff0c;CopyOnWriteArrayList 是一种特殊的线程安全的集合类。它位于 java.util.concurrent 包中&#xff0c;主要用于在并发读写场景下提供稳定的性能。与传统的 ArrayList 不同&#xff0c;CopyOnWriteArrayList 通过在每次修改时创建一个…

简单编程实现QT程序黑色主题显示

代码如下 int main(int argc, char *argv[]) {QApplication a(argc, argv);//QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat());QPalette darkpalette;a.setStyle(QStyleFactory::create("Fusion"));darkpalette.setColor(QPalette::Wind…

沁恒CH32V208GBU6外设PWM:注意分辨时钟使能函数RCC_APB2PeriphClockCmd;PWM模式1和模式2的区别;PWM动态开启和关闭

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…

飞书企业消息实践

一、飞书自带的消息机器人限制 频控策略 - 服务端 API - 飞书开放平台 自定义机器人的频率控制和普通应用不同&#xff0c;为单租户单机器人 100 次/分钟&#xff0c;5 次/秒。建议发送消息尽量避开诸如 10:00、17:30 等整点及半点时间&#xff0c;否则可能出现因系统压力导致…

0107作业

思维导图 练习: 要求在堆区连续申请5个int的大小空间用于存储5名学生的成绩&#xff0c;分别完成空间的申请、成绩的录入、升序 排序、 成绩输出函数以及空间释放函数&#xff0c;并在主程序中完成测试 要求使用new和delete完成 #include <iostream>using namespace std…

以C++为基础快速了解C#

using System: - using 关键字用于在程序中包含 System 命名空间。 一个程序一般有多个 using 语句, 相当于C的 using namespace std; C# 是大小写敏感的。 所有的语句和表达式必须以分号&#xff08;;&#xff09;结尾。 程序的执行从 Main 方法开始。 与 Java 不同的是&#…

面试题:并发与并行的区别?

并发&#xff08;Concurrency&#xff09;和并行&#xff08;Parallelism&#xff09;是计算机科学中两个相关但不同的概念&#xff0c;它们都涉及到同时处理多个任务&#xff0c;但在实现方式和效果上有显著的区别。理解这两者的区别对于编写高效的多任务程序非常重要。 并发&…

面向对象分析和设计OOA/D,UML,GRASP

目录 什么是分析和设计&#xff1f; 什么是面向对象的分析和设计&#xff1f; 迭代开发 UML 用例图 交互图 基于职责驱动设计 GRASP 常见设计原则 什么是分析和设计&#xff1f; 分析&#xff0c;强调是对问题和需求的调查研究&#xff0c;不是解决方案。例如&#x…

MySQL使用navicat新增触发器

找到要新增触发器的表&#xff0c;然后点击设计&#xff0c;找到触发器标签。 根据实际需要&#xff0c;填写相关内容&#xff0c;操作完毕&#xff0c;点击保存按钮。 在右侧的预览界面&#xff0c;可以看到新生成的触发器脚本

Anthropic 的人工智能 Claude 表现优于 ChatGPT

在人工智能领域&#xff0c;竞争一直激烈&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;技术的发展中&#xff0c;多个公司都在争夺市场的主导地位。OpenAI的ChatGPT和Anthropic的Claude是目前最具影响力的两款对话型AI产品&#xff0c;它们都能够理解并生成自然…

《罪恶装备-奋战》官方中文学习版

《罪恶装备 -奋战-》是Arc System Works开发的格斗游戏《罪恶装备》系列的第二十五部作品 [1]&#xff0c;男主角索尔历时25年的故事就此画上句号&#xff0c;而罪恶装备的故事却并未结束。 《罪恶装备-奋战》官方中文版 https://pan.xunlei.com/s/VODWAm1Dv-ZWVvvmUMflgbbxA1…

期末概率论总结提纲(仅适用于本校,看文中说明)

文章目录 说明A选择题1.硬币2.两个事件的关系 与或非3.概率和为14.概率密度 均匀分布5.联合分布率求未知参数6.联合分布率求未知参数7.什么是统计量&#xff08;记忆即可&#xff09;8.矩估计量9.117页12题10.显著水平阿尔法&#xff08;背公式就完了&#xff09; 判断题11.事件…

流程图(四)利用python绘制漏斗图

流程图&#xff08;四&#xff09;利用python绘制漏斗图 漏斗图&#xff08;Funnel Chart&#xff09;简介 漏斗图经常用于展示生产经营各环节的关键数值变化&#xff0c;以较高的头部开始&#xff0c;较低的底部结束&#xff0c;可视化呈现各环节的转化效率与变动大小。一般重…

继承(5)

大家好&#xff0c;今天我们继续来学习继承的相关知识&#xff0c;来看看子类构造方法&#xff08;也叫做构造器&#xff09;是如何做的。 1.6 子类构造方法 父子父子,先有父再有子,即:子类对象构选时,需要先调用基类构造方法,然后执行子类的构造方法 ★此时虽然执行了父类的…

Vue框架主要用来做什么?Vue框架的好处和特性.

在快速发展的互联网时代&#xff0c;前端开发技术的变革日新月异&#xff0c;为开发者带来了前所未有的机遇与挑战。Vue.js&#xff0c;作为前端开发领域的一颗璀璨新星&#xff0c;以其轻量级、高效灵活的特性&#xff0c;赢得了广大开发者的青睐。本文将深入探讨Vue框架的主要…