Linux--线程的认识(一)

线程的概念

线程(Thread)是操作系统中进行程序执行的最小单位,也是程序调度和分派的基本单位。它通常被包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

像之前所有执行的程序,都是用main作为主函数,单线程执行的;一切的语句都是在main函数中从上至下依次进行的;如果一条语句阻塞了,那么整个进程都将阻塞;

线程的特点

并发执行线程是进程内的一条执行路径或控制单元,因此多个线程可以在同一进程中并发执行,共享进程的资源(如内存空间、文件句柄等)。

独立调度线程作为系统调度的基本单位,系统能独立地分配CPU给线程,从而确保每个线程都能独立运行。

轻量级:线程的创建、销毁和切换比进程更快速,因为线程间的资源共享减少了资源分配和回收的开销。

同步与互斥:由于多个线程可能同时访问共享资源,因此需要使用同步机制(如互斥锁、条件变量等)来确保数据的一致性和正确性。

多线程编程:多线程编程模型允许开发者编写能够并发执行多个任务的应用程序,以提高程序的性能和响应能力。

线程的主要特点就是能够在进程中并发执行,对于一项任务来说,如果是流程分布的,那么单人打工和多人打工的效率可想而知,多线程的效率能够大大提高;并且相对于进程来说,它的开销更小,也就是比进程的量级小,这样我们可以有效利用资源,提高一切有用效率。

线程与进程的区别

在这里插入图片描述
进程是操作系统资源分配和调度的基本单位

线程是进程的一部分,是CPU调度和分配的基本单位

每个进程都拥有自己独立的地址空间和系统资源,进程之间的资源是独立的;

线程不拥有系统资源,它们共享其所属进程的资源

每个独立的进程都有一个程序运行的入口、顺序执行序列和程序入口

线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制;

线程如何访问到内存(页表的进一步理解)

之前我们一直讲述,进程拥有自己的进程地址空间,上面的地址都是虚拟地址,需要通过页表的映射找到对应的物理内存;

那对于线程是如何找到对应内存的?

线程本身并不通过页表映射物理内存找到对应物理地址。线程是进程的一部分,它们共享进程的地址空间,包括进程的页表。当线程在访问内存时,实际上是进程在进程内存访问。因此页表的映射过程是在进程层面进行的

下面简述线程是如何找到对应内存的:

    1. 虚拟地址的生成:当线程需要访问内存时,它会在进程中产生一个虚拟地址,这个虚拟地址就是在进程地址空间的。
    1. 页表查找:CPU使用虚拟地址的页号部分作为索引来查找进程的页表。页表是一个包含多个表项的数据结构,每个页表都对应一个虚拟页面,并记录该页面在物理内存中的位置或其他相关信息。(类似于我们找到一本书,翻开目录查找相应页数中的内容);

在这里插入图片描述

    1. 页表项解析:CPU从页表中获取与虚拟地址对应的页表项。
    1. 构建物理地址:CPU将页表项中的物理页帧号与虚拟地址的页内偏移量相结合,生成一个完整的物理地址。该物理地址指向物理内存中实际存储数据的位置。
    1. 内存访问:CPU使用这个物理地址来访问内存中的数据。如果页面已经存在于物理内存中(即该页面已经被加载到内存中),则CPU可以直接从物理内存中读取或写入数据(共享内存)。如果页面不存在于物理内存中(即发生了页错误),则操作系统将触发页面置换算法来选择一个页面进行置换,并将所需的页面从磁盘或其他存储介质中加载到物理内存中。

线程的控制

简单使用

void* newthreadrun()
{
      while(true)
    {      
        cout<<"this is new thread:"<<getpid()<<endl;
        sleep(1);
    }
}

int main()
{
    //1.id
    //2对于新线程和主线程哪个先运行,由调度器决定
    pthread_t tid;
    void* ret=nullptr;
    pthread_create(&tid,nullptr,newthreadrun,nullptr);
    
    while(true)
    {
        cout<<"this a main thread:"<<getpid()<<endl;
        sleep(1);
    } 
    return 0;
}

有关函数:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,  
                void *(*start_routine) (void *), void *arg);

参数说明:

pthread_t *thread:这是一个指向 pthread_t 类型的指针,用于获取新创建线程的标识符。这个标识符可以在其他线程函数中被引用,以便进行线程间的同步或等待其他线程结束。
const pthread_attr_t *attr:这是一个指向 pthread_attr_t 类型的指针,用于设置线程属性。大多数情况下,这个参数可以设置为 nullptr,使用默认的线程属性。
void *(*start_routine) (void *):这是一个指向线程函数的指针,当新线程被创建时,这个函数将被调用。这个函数应该返回一个 void * 类型的指针,并且接受一个 void * 类型的参数。
void *arg:这是传递给线程函数的参数。它可以是任何数据类型,但通常会被强制转换为 void * 类型。在线程函数内部,你可以将其转换回原来的类型。
函数返回值

如果成功,pthread_create 将返回 0。
如果失败,它将返回一个错误码,你可以使用 strerror 或 perror 函数来获取关于这个错误码的详细信息。

在这里插入图片描述
结果:
在这里插入图片描述
查看进程信息:
在这里插入图片描述
查看线程信息:
在这里插入图片描述
指令ps-aL:用于显示当前系统中进程和线程的信息;
PID:进程ID
LWP:light weight process 轻量级进程呈(给客户就是对应的线程)

id

string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}
void* newthreadrun()
{
    while(true)
    {
        cout<<"this is new thread:"<<getpid()<<endl;
        cout<<"newthread thread id: "<<ToHex(pthread_self())<<endl;
        sleep(1);
    }
}

int main()
{
    //1.id
    //2.对于新线程和主线程哪个先运行,由调度器决定
    pthread_t tid;
    pthread_create(&tid,nullptr,newthreadrun,nullptr);

    while(true)
    {
        cout<<"this a main thread:"<<getpid()<<endl;
        cout<<"main thread id: "<<ToHex(pthread_self())<<endl;
         sleep(1);
    }   
    return 0;

}

在这里插入图片描述
在这里插入图片描述

传参

string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}
void* newthreadrun(void* args)
{
    string threadname=(char*)args;
    
    while(true)
    { 
        cout<<threadname<<endl;
        cout<<"this is new thread:"<<getpid()<<endl;
        cout<<"newthread thread id: "<<ToHex(pthread_self())<<endl;
        sleep(1);
    }
}

int main()
{
    //1.id
    //2对于新线程和主线程哪个先运行,由调度器决定
    pthread_t tid;
    void* ret=nullptr;
    pthread_create(&tid,nullptr,newthreadrun,(void*)"thread-1");//传参
    while(true)
    {
        
        cout<<"this a main thread:"<<getpid()<<endl;
        cout<<"main thread id: "<<ToHex(pthread_self())<<endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

等待退出

pthread_join 可以确保线程的资源得到正确的清理。当一个线程终止时,它的资源(如栈内存)不会自动释放,直到另一个线程调用 pthread_join 或线程是分离的(通过 pthread_detach 或设置属性)。如果线程没有被连接或分离,那么它的资源将不会被释放,这可能会导致内存泄漏。

string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}
void* newthreadrun(void* args)
{
    string threadname=(char*)args;
    
    while(true)
    { 
        cout<<threadname<<endl;
        cout<<"this is new thread:"<<getpid()<<endl;
        cout<<"newthread thread id: "<<ToHex(pthread_self())<<endl;
        sleep(1);
    }
}

int main()
{
    //1.id
    //2对于新线程和主线程哪个先运行,由调度器决定
    pthread_t tid;
    void* ret=nullptr;
    pthread_create(&tid,nullptr,newthreadrun,(void*)"thread-1");//传参
    while(true)
    {
        
        cout<<"this a main thread:"<<getpid()<<endl;
        cout<<"main thread id: "<<ToHex(pthread_self())<<endl;
        sleep(1);
    }

	int n=pthread_join(tid,&ret);
    cout << n << endl;
    sleep(1);
    return 0;
}

有关函数:

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

pthread_t thread:这是你想要等待的线程的标识符。这个标识符是通过 pthread_create函数返回的。
void **retval:这是一个指向指针的指针,用于获取被等待线程的返回值。如果 retval 不是 nullptr,那么 pthread_join 将把被等待线程的返回值存储在 retval 所指向的位置。如果被等待线程没有返回值(即线程函数返回 NULL 或 pthread_exit 被调用时没有指定返回值),则 *retval 的内容将是不确定的。如果你不关心线程的返回值,可以将 retval 设置为 NULL。

函数返回值:

如果成功,pthread_join 将返回 0。
如果失败,它将返回一个错误码。

结果:
在这里插入图片描述

修改:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果主线程先退出,新线程会怎么样
在这里插入图片描述

资源共享

资源共享是指多个线程可以访问和使用同一进程中的某些资源。

int g_val = 100;
string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}
void* newthreadrun(void* args)
{
    string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
        printf("new thread, g_val: %d, &g_val: %p\n", g_val, &g_val);
        g_val++;
        sleep(1);
    }
   pthread_exit((void*)123);
}
int main()
{   
    pthread_t tid;
    void* ret=nullptr;
    pthread_create(&tid,nullptr,newthreadrun,(void*)"thread-1");//传参
    while(cnt--)
    {
         printf("main thread, g_val: %d, &g_val: %p\n", g_val, &g_val);
       
         sleep(1);
    }    
    int n=pthread_join(tid,&ret);
    cout<<"main thread quit: "<<n<<" main thread get a ret: "<<(long long)ret<<endl;
    sleep(1);
    return 0;
}

结果:
在这里插入图片描述

新线程出错

在这里插入图片描述

线程的终止

在这里插入图片描述

正确操作:
在这里插入图片描述
在这里插入图片描述

线程的优缺点

线程的优缺点其实上面的简述都有提及,下面就来总结下:

优点:

  • 资源共享:线程共享它们所属的进程的资源,包括内存地址空间、全局变量、文件句柄等。这使得线程间的通信和数据共享变得容易。

  • 减少开销:线程的创建和销毁比进程的创建和销毁所需的资源要少得多。因此,在需要频繁创建和销毁执行单元的情况下,使用线程更为高效。

  • 独立性:线程是独立的执行路径,它们可以并发执行,互不干扰。这有助于提高程序的并行性和响应能力。

  • 多处理器支持:多线程可以充分利用多处理器的优势,实现真正的并行处理。通过将一个任务分解为多个线程,可以同时在多个处理器上执行这些线程,从而加快任务的完成速度。

  • 简化编程:在某些情况下,使用线程可以简化编程。例如,可以使用线程来实现异步操作或并行计算等复杂任务。

缺点:

  • 资源竞争:由于线程共享进程的资源,因此可能会出现资源竞争的问题。当多个线程同时访问同一资源时,可能会导致数据不一致或其他问题。为了解决这个问题,需要使用同步机制(如锁、信号量等)来确保线程之间的协调。
  • 编程复杂性:线程编程通常比进程编程更为复杂。程序员需要处理线程间的同步和通信问题,以及避免死锁、竞态条件等潜在问题。
  • 系统稳定性:多线程程序可能更容易出现错误和崩溃。当多个线程并发执行时,它们可能会互相干扰或竞争资源,从而导致程序的不稳定或崩溃。此外,如果线程管理不当(如创建过多的线程),也可能导致系统资源的耗尽和性能下降。
  • 安全性问题:多线程程序中可能存在安全问题。例如,如果一个线程可以访问另一个线程的私有数据或执行敏感操作,那么可能会导致数据泄露或系统被攻击。为了解决这个问题,需要使用访问控制和其他安全机制来保护线程之间的数据和操作。

线程的资源

线程的私有资源

  • 线程栈(Thread Stack):每个线程都有自己独立的栈空间,用于存储局部变量、函数调用和返回地址等信息。线程栈在创建线程时分配,并在线程结束时释放。
  • 线程ID(Thread ID):每个线程都有一个唯一的标识符,称为线程ID,用于在操作系统中唯一标识该线程。
  • 寄存器上下文(Register Context):线程在执行过程中会使用到各种寄存器,如程序计数器(PC)、栈指针(SP)等。这些寄存器的状态对于每个线程来说都是私有的,并在线程切换时被保存和恢复。
  • 线程本地存储(Thread-Local Storage, TLS):线程本地存储是一种特殊的存储区域,允许每个线程存储其私有的全局变量。这些变量在逻辑上是全局的,但在物理上每个线程都有自己独立的副本。
  • 错误处理:每个线程都有自己的错误处理上下文,包括异常处理机制、错误码等。这些机制允许线程独立地处理自己遇到的错误和异常情况。

线程的共享资源

  • 代码段(Code Segment):进程中的代码段是共享的,包括程序中的函数、变量定义等。多个线程可以并发地执行相同的代码段。
  • 数据段(Data Segment):进程中的全局变量和静态变量位于数据段,这些数据对于进程中的所有线程都是可见的。然而,线程对这些数据的访问需要同步机制来避免竞态条件和数据不一致的问题。
  • 堆(Heap):进程中的堆空间也是共享的,用于动态分配内存。多个线程可以同时从堆中分配和释放内存,但同样需要同步机制来确保内存访问的安全性和一致性。
  • 文件描述符(File Descriptors):进程打开的文件和其他I/O资源(如套接字)由文件描述符表示。这些文件描述符对于进程中的所有线程都是共享的,允许线程之间共享文件和I/O操作。
  • 信号(Signals):操作系统发送给进程的信号也是共享的。当进程收到一个信号时,操作系统会选择一个线程来处理该信号。通常,进程的主线程或特定的信号处理线程会负责处理这些信号。
  • 进程环境:进程的环境变量、打开的文件句柄、信号掩码等也是共享的,但它们通常被视为进程级别的资源,而不是直接由线程管理的资源。

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

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

相关文章

【leetcode面试经典150题】-80. 删除有序数组中的重复项 II

【leetcode面试经典150题】-80. 删除有序数组中的重复项 II 1 题目介绍2 个人解题思路2.1 代码2.2 思路 3 官方题解 1 题目介绍 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组…

【DZ模板】克米设计APP手机版本地化+完美使用

模版介绍 【DZ模板】价值288克米设计APP手机版DZ模板 数据本地化完美使用 腾讯官方出品discuz论坛DIY的后台设置&#xff0c;功能齐全&#xff0c;论坛功能不亚于葫芦侠&#xff0c;自定义马甲&#xff0c;自定义认证&#xff0c;自定义广告&#xff0c;完全可以打造出自己想…

Redis教程(十五):Redis的哨兵模式搭建

一、搭建Redis一主二从 分别复制三份Redis工作文件夹&#xff0c;里面内容一致 接着修改7002的配置文件&#xff0c;【redis.windows-service.conf】 port 7002 改成 port 7002 slaveof 127.0.0.1 7001 7003也同样修改 port 7003 slaveof 127.0.0.1 7001 这样就指定了700…

从0开始带你成为Kafka消息中间件高手---第三讲

从0开始带你成为Kafka消息中间件高手—第三讲 实际上来说&#xff0c;每次leader接收到一条消息&#xff0c;都会更新自己的LEO&#xff0c;也就是log end offset&#xff0c;把最后一位offset 1&#xff0c;这个大家都能理解吧&#xff1f;接着各个follower会从leader请求同…

读人工智能时代与人类未来笔记14_管控人工智能

1. 管控人工智能 1.1. 历史上的战场进一步推进到与数字网络相连的所有地方 1.2. 数字程序现在控制着一个由众多实体系统构成的庞大且仍在不断增长的领域&#xff0c;而且越来越多的此类系统已实现网络化 1.2.1. 在某些情况下甚至连门锁和冰箱都实现了网络化 1.2.2. 这催生出…

vue3中element-plus下拉菜单与图标的使用

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

十、通配符和正则表达式

10.1 通配符 通配符是由shell处理的, 它只会出现在 命令的“参数”里。当shell在“参数”中遇到了通配符 时&#xff0c;shell会将其当作路径或文件名去在磁盘上搜寻可能的匹配&#xff1a;若符合要求的匹配存在&#xff0c;则进 行代换(路径扩展)&#xff1b;否则就将该通配…

21.Happens-Before原则

文章目录 Happens-Before原则1.Happens-Before规则介绍2.规格介绍2.1.顺序性规则(as-if-serial)2.2.volatile规则2.3.传递性规则2.4.监视锁规则2.5.start规则2.6.join()规则 Happens-Before原则 JVM内存屏障指令对Java开发工程师是透明的&#xff0c;是JMM对JVM实现的一种规范和…

HE TB PPDU MU-RTS

看起来像是MU-RTS的触发帧的应答不是HE TB PPDU&#xff0c;而是传统得的帧&#xff0c;应答CTS。 非AP 的STA&#xff0c;是不能发送触发帧&#xff0c;也就是说&#xff0c;触发帧&#xff0c;只能是由AP发送给STA

RedHat9 | DNS剖析-配置主DNS服务器实例

一、实验环境 1、BIND软件包介绍 BIND软件是一款开放源码的DNS服务器软件&#xff0c;由美国加州大学Berkeley分校开发和维护&#xff0c;全称为Berkeley Internet Name Domain。该软件在DNS&#xff08;域名系统&#xff09;领域具有重要地位&#xff0c;是目前世界上使用最…

网站笔记:huggingface model memory calculator

Model Memory Utility - a Hugging Face Space by hf-accelerate 这个工具可以计算在 Hugging Face Hub上托管的大型模型训练和执行推理时所需的vRAM内存量。模型所需的最低推荐vRAM内存量表示为“最大层”的大小&#xff0c;模型的训练大约是其大小的4倍&#xff08;针对Adam…

AI播客下载:The Logan Bartlett Show Podcast(AI创业投资主题)

Logan Bartlett Show Podcast是一个播客&#xff0c;主持人Logan Bartlett与科技界的领导者以及投资者进行对话&#xff0c;讨论他们在运营或投资企业中学到的经验教训&#xff0c;主要集中在科技创投领域。 Logan Bartlett 是 Redpoint Ventures 的投资人&#xff0c;并且在该…

用户态下屏蔽全局消息钩子 —— ClientLoadLibrary 指针覆盖

目录 前言 一、研究 SetWindowsHookEx 的机制 二、概念验证 三、运行效果分析 四、总结与展望 参考文献 原文出处链接&#xff1a;[https://blog.csdn.net/qq_59075481/article/details/139206017] 前言 SetWindowsHookEx 函数帮助其他人员注入模块到我们的进程&#x…

蓝桥杯嵌入式国赛笔记(2):拓展板按键程序设计

目录 1、前言 2、电路原理 3、代码编写 3.1 读取Btn电压 3.2 检索按键 3.3 main文件编写 3.3.1 进行变量定义 3.3.2 AD_Key函数 3.3.3 LCD函数 3.3.4 main函数 3.3.5 完整代码 4、测试 5、总结 1、前言 本文进行拓展板按键程序设计&#xff0c;拓展板的按键是通…

Pycharm在下载安装第三方库时速度慢或超时问题 / 切换国内镜像地址

pycharm下载第三方库速度极慢&#xff0c;搜索了一下&#xff0c;发现方法非常乱&#xff0c;稍作整理。这个问题一般都会出现&#xff0c;在我们开发中遇到的常见问题&#xff0c;根据以下解决方法&#xff0c;基本可以解决&#xff0c;但是不能100%保证 Installing packages …

上位机图像处理和嵌入式模块部署(f103 mcu中的看门狗)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 项目开发中&#xff0c;我们总是希望固件代码能够稳定、健壮。为此&#xff0c;我们进行了各种软件质量管理&#xff0c;从需求开发开始&#xff0…

react组件传参 父传子可以传字符串,布尔值,数组,对象,jsx,

在react中&#xff0c;父传子组件 props的灵活性是很强大的&#xff0c;可以传字符串&#xff0c;布尔值&#xff0c;数组&#xff0c;对象&#xff0c;jsx&#xff0c; function Son(props) {console.log(props,"props的值")return(<div>这是儿子组件 {props.…

LeetCode 第131场双周赛个人题解

100309. 求出出现两次数字的 XOR 值 原题链接 求出出现两次数字的 XOR 值 - 力扣 (LeetCode) 竞赛 思路分析 签到题&#xff0c;一次遍历 AC代码 class Solution:def duplicateNumbersXOR(self, nums: List[int]) -> int:cnt Counter(nums)res 0st set(nums)for x …

Go微服务: Http服务注册在Consul的示例(非Go-Micro)

概述 现在&#xff0c;我们使用consul客户端的api来把Http服务注册到consul上&#xff0c;非Go-Micro的形式其实&#xff0c;consul官方提供了对应的接口调用来实现&#xff0c;golang中的consul/api包对其进行了封装我们使用consul/api来进行展示 目录结构 gitee.com/go-mi…

插入耳麦耳机的麦克风线,但录入声音失败的解决办法

现状&#xff1a;可以查看到有麦克风&#xff0c;但说话时&#xff0c;音量条没有上升 解决&#xff1a;控制面板-语音识别。对头戴式耳机进行操作。 效果&#xff1a;完成后&#xff0c;用搜狗语音打字验证通过。 注意&#xff1a;电脑上还连接了个有线音箱&#xff0c;但说…