Linux 线程概念及线程控制

news2024/11/26 15:38:04

1.线程与进程的关系

执行流(Execution Flow)通常指的是程序执行过程中的控制路径,它描述了程序从开始到结束的指令执行顺序。例如我们要有两个执行流来分别进行加法和减法的运算,我们可以通过使用 fork 函数来创建子进程,父进程进行加法运算,子进程进行减法的运算来完成任务,但是创建一个进程需要额外创建PCB,进程地址空间,页表等等,代价过大效率过低,于是出现了线程的概念。

在Linux系统中,线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中。每个线程都有自己的运行状态,包括程序计数器、寄存器集和栈。

在Linux内核中,并没有严格地区分进程和线程,它们都是通过task_struct数据结构来表示的。每个进程或线程都有一个对应的task_struct实例,其中包含了该进程或线程的许多重要信息。因此,可以将线程看作是共享进程地址空间的轻量级进程。 

线程是进程内的执行单元。在同一进程中创建的所有线程都共享相同的地址空间、全局变量和其他资源。这意味着,线程间数据交换的成本较低,不需要额外的复制操作。然而,这同时也意味着,任何一个线程对共享资源的更改都会直接影响到其它线程。

进程 = 内核数据结构 + 代码和数据

上图中,多个PCB,进程地址空间,页表等都属于进程的内核数据结构,因此这些所有内容加起来,才算做进程。

线程 = 内核数据结构 + 共享的代码和数据

进程是承担系统资源分配的基本实体,操作系统分配资源时,以进程为单位,当一个进程拿到资源后,再去分配给不同的线程。线程和进程有如下图的关系:

#include <iostream>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int gval = 100;
void *ThreadRountine(void *args)
{
    char* name = static_cast<char*>(args);
    cout << name << " is running,&gval= " << &gval << endl;
    return nullptr;
}
int main()
{
    vector<pthread_t> threads;
    for (int i = 0; i < 5; i++)
    {
        pthread_t tid;
        string name = "Thread" + to_string(i);
        pthread_create(&tid, nullptr, ThreadRountine, (void *)name.c_str());
        sleep(1);
    }
    return 0;
}

我们可以看到多个进程使用的是同一个gval,即线程共享相同的地址空间、全局变量和其他资源。

当然如果我们想要在不同的线程里创建全局变量,即全局变量线程各自有一份,就需要用到关键字thread_local或__thread(thread前有两个下划线)。

推荐使用thread_local,在 C++11 及以后的版本中,thread_local 关键字用于声明线程局部变量。这些变量在每个线程中都有独立的实例,它们的生命周期与线程的生命周期绑定。thread_local 变量在第一次使用时初始化,并在线程结束时自动销毁。使用 thread_local 变量可以避免数据竞争和不必要的同步,因为每个线程操作的是自己的数据副本。

thread_local int gval = 100;

2.Thread Control Block (TCB)

Linux中,由于线程和进程的内核数据结构相似,所以两者共用一套PCB数据结构来表示,但大部分操作系统描述管理线程,并不是使用PCB,而是额外设计了Thread Control Block (TCB)。

Thread Control Block (TCB) 是操作系统中用于管理和控制线程的数据结构。在上图中,多个TCB分别控制不同部分的代码,CPU调度线程时,也去调度TCB。比如主流的WindowsMacOS等操作系统,都使用这样的方式。它是线程在系统中的核心表示,存储了线程的关键属性、状态信息以及与线程生命周期管理相关的数据。TCB 包含了以下主要信息:

  • 线程标识符 (TID):唯一标识线程的编号。
  • 线程状态:如就绪、运行、阻塞、结束等,指示线程当前的活动状态。
  • 调度信息:包括线程的优先级、时间片、调度策略等,这些信息决定了线程何时以及如何被调度执行。
  • 上下文保存:包括CPU寄存器状态(如程序计数器、通用寄存器、状态寄存器等),这些信息在线程切换时需要保存和恢复。
  • 资源关联:关联了线程使用的系统资源,如私有堆栈、共享资源的锁定状态、等待队列链接等。

3. 轻量级进程 LWP

在Linux中,一个进程可以有多个PCB,当PCB的数目为1,那么这个PCB可以代表一个进程;如果PCB数目有多个,那么这个PCB代表一个进程的多个线程。因此在Linux中,没有真正的线程,一个执行流由一个PCB维护。Linux把这种介于线程与进程之间的状态,称为轻量级进程 LWP (Light Weight Process)。

4. 进程资源分配给线程的机制

进程通过页表机制实现虚拟地址空间到物理地址空间的映射,从而管理和分配资源。在多线程的场景中,尽管有多个执行流(线程),但它们共享同一进程的虚拟地址空间和页表。这意味着所有线程可以访问进程的代码段、数据段、堆和栈等资源,而无需为每个线程单独分配这些资源。

当创建一个新线程时,它并不需要一个全新的页表,而是继承其父进程的页表。这样,线程可以直接使用进程已经映射到物理内存的虚拟地址。这种共享页表的方式极大地减少了内存的消耗,并且简化了线程的创建和管理过程。

不论是内存还是磁盘,都被划分为了以4kb为单位的数据块,一个数据块可以被称为页框 / 页帧。

操作系统管理内存,或者管理磁盘,都是以4kb为基本单位的。比如把磁盘中的数据加载到内存中,就是以4kb为基本单位进行拷贝。页框是被struct page管理的,Linux 2.6.10中,struct page源码如下:

struct page 
{
	page_flags_t flags;		
	atomic_t _count;		
	atomic_t _mapcount;	
	unsigned long private;		
	struct address_space *mapping;
	pgoff_t index;
	struct list_head lru;	
	
#if defined(WANT_PAGE_VIRTUAL)
	void *virtual;	
#endif 
};

操作系统想要管理所有的页框,只需要创建一个数组,数组的元素类型是struct page。此时操作系统对内存或磁盘的管理,就变成了对数组的增删查改。而且从上方的struct page源码中可以发现,它是不存储页框的起始地址和终止地址,因为可以通过下标计算出起始地址,起始地址 + 4kb就可以求出终止地址。

我们以32位操作系统为例,页表的结构如下:

页表的任务是把虚拟地址解析为物理地址,当传入一个虚拟地址,页表就要对其解析。一个32位的地址,会被分为三部分,第一部分是前10位,第二部分是中间10位,第三部分是末尾12位。

第一部分就是上图中的深蓝色部分,其由页目录进行解析。2^10 = 1024,即前10位地址有1024种可能,而页目录就是一个长度为1024的数组。解析地址时,先通过前10位,在页目录中找到对应的下标。每个页目录的元素,指向一个页表。

第二部分是上图中的黄色部分,其由页表进行解析,同样的 2^10 = 1024,即中间10位地址也1024种可能,所以每个页表的长度也是1024。解析中间10位时,在页表中找到对应的下标,从而找到对应的内存。

第三部分时上图中的绿色部分。一个数据块大小是4 kb,这是内存管理的基本单位。而 2^12 byte = 4 kb,因此第三部分也叫做页内偏移,通过前两个部分,我们已经可以锁定到内存中的一个页框了,而第三部分存储的是物理地址相对于页框起始地址的偏移量,此时就可以根据起始地址 + 偏移量来确定一个地址。

以上就是页表解析地址的全过程。

5. 线程控制

Linux系统并不像某些其他操作系统那样提供由内核直接提供的线程库,因为本质上Linux是没有线程的,而是通过轻量级进程来模拟线程,所以Linux是在用户层实现了一套多线程方案,以库的方式提供给用户使用。最常用的线程库是POSIX线程库(pthread),它提供了创建、终止、同步和调度等一系列函数来管理线程。

故而在使用gcc / g++编译时,要带上选项 -l pthread,来引入原生线程库。例如:

g++ -o test.exe test.cpp -l pthread

线程创建 (pthread_create)

pthread_create函数是POSIX线程库(pthreads)中用于创建新线程的标准接口,包含在头文件<pthread.h>中。它允许用户指定线程的属性、线程函数及其参数,并返回新创建线程的标识符TID。这个函数在多线程编程中非常重要,因为它是启动新执行流程的基础。

参数:

  • pthread_t *thread:指向一个pthread_t类型的变量的指针,该变量将接收新创建线程的标识符TID。
  • const pthread_attr_t *attr:指向线程属性的指针。如果为NULL,则使用默认属性。
  • void *(*start_routine) (void *):线程函数的入口点,其签名必须匹配此形式,接受一个void *类型的参数并返回同样类型的值。
  • void *arg:传递给线程函数的参数。

返回值:

  • 如果创建成功,返回0
  • 如果创建失败,返回错误码
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
void *ThreadRountine(void *arg)
{
    string name = static_cast<char*>(arg);
    while (1)
    {
        cout << "I am " << name << ",mypid is " << getpid() << endl;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRountine, (void *)"Thread-1");
    while (1)
    {
        cout << "I am Thread_main,mypid is " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

在上述代码中,我们创建了一个线程 Thread-1,并且在主线程和新线程都在循环输出一段话,而输出的语句中我们发现主线程和新线程的 pid 相同,原因是他们在同一进程中,故 pid 相同,而我们区分不同线程是通过 tid 。

pthread_self

pthread_self 是 POSIX 线程库中的一个函数,它用于获取调用该函数的线程的唯一标识符。这个标识符是 pthread_t 类型的,可以用于识别和区分同一进程中的不同线程。pthread_self 在多线程编程中非常有用,尤其是在需要进行线程同步或管理线程资源时。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;

void *ThreadRountine(void *arg)
{
    string name = static_cast<char *>(arg);
    while (1)
    {
        cout << "I am " << name << ",mypid is " << getpid() << ",mytid is" << pthread_self()<< endl;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRountine, (void *)"Thread-1");
    while (1)
    {
        cout << "I am Thread_main,mypid is " << getpid() << ",mytid is" << pthread_self() << endl;
        sleep(1);
    }
    return 0;
}

我们发现线程 tid 很大,因为这个 tid 表示的是线程在内存的物理地址。

我们知道线程控制是通过pthread库实现的。在pthread动态库中线程被结构体描述,同时再被数据结构组织。

线程终止

pthread_exit

pthread_exit 是 POSIX线程库中的一个重要函数,用于从线程中退出。相比于简单地让函数返回,pthread_exit 允许传递一个 exit status 给可能正在等待该线程结束的其他线程或函数。

参数:

  • retval: 此参数是可选的,通常用来向主线程或其他等待该线程结束的线程传递退出状态。我们知道线程创建时函数的返回值为void*类型,retval就是用来终止线程然后代替返回值。

pthread_cancel

pthread_cancel 是 POSIX 线程库中的一个函数,用于向指定的线程发送取消请求。当一个线程收到取消请求后,它将进入取消状态,并根据线程的取消类型和取消点的设置,决定取消请求的处理方式。线程可以配置为在到达取消点时立即响应取消请求,或者延迟响应直至下一个取消点。

参数:

  • thread: 要发送取消请求的线程的 TID。

返回值:

  • 如果函数成功发送取消请求,返回 0;
  • 如果发送取消请求失败,返回非零值。

pthread_exit函数用于使当前线程正常退出,pthread_cancel函数用于向另一个线程发送取消请求。

线程等待(pthread_join)

pthread_join 函数是 POSIX 线程库中的一个同步函数,它用于等待一个或多个线程终止。当一个线程调用 pthread_join 并传递另一个线程的 ID 时,调用该函数的线程将被阻塞,直到被等待的线程结束执行。一旦被等待的线程终止,pthread_join 函数返回,并且可以选择性地获取该线程的退出状态。如果不需要获取退出状态,可以将第二个参数设置为 NULL

参数:

  • thread:等待的线程的TID
  • retval:输出型参数,线程退出后,该参数会接收到退出线程的函数返回值

返回值:

  • 如果等待成功返回 0
  • 如果失败返回错误码
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
void *ThreadRountine(void *arg)
{
    string name = static_cast<char *>(arg);
    int cnt=3;
    while (cnt--)
    {
        cout << "I am " << name << ",mypid is " << getpid() << ",mytid is " << pthread_self()<< endl;
        sleep(1);
    }
    return (void*)123456;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRountine, (void *)"Thread-1");
    void* retval;
    pthread_join(tid,&retval);
    printf("Thread-1 return value is %lld\n",(long long)retval);
    return 0;
}

线程分离(pthread_detach)

pthread_detach 函数用于将一个线程设置为分离状态(detached state)。在分离状态下,当线程结束时,它所占用的资源会自动被操作系统回收,无需其他线程显式调用 pthread_join 来等待其结束。这意味着,一旦线程被设置为分离状态,它就不再与创建它的线程绑定,可以独立存在直至结束。

参数:

  • thread:要设置为分离状态的线程的标识符TID。

返回值:

  • 如果函数成功,返回值为 0
  • 如果函数失败,返回对应的错误码。

使用 pthread_detach 的场景通常是在那些不需要等待线程结束或者不关心线程退出状态的情况下。例如,在一个长时间运行的服务器程序中,主线程可能会创建多个子线程来处理客户端请求,而主线程本身需要继续监听新的请求。在这种情况下,可以在子线程的执行函数中通过调用 pthread_detach(pthread_self()) 来设置子线程为分离状态,这样即使主线程不等待子线程结束,子线程在完成任务后也能正确地释放资源. 

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
void *ThreadRountine(void *arg)
{
    pthread_detach(pthread_self());
    string name = static_cast<char *>(arg);
    int cnt=3;
    while (cnt--)
    {
        cout << "I am " << name << ",mypid is " << getpid() << ",mytid is " << pthread_self()<< endl;
        sleep(1);
    }
    return (void*)123456;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRountine, (void *)"Thread-1");
    return 0;
}

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

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

相关文章

智慧商城项目2-登录模块

登录页静态布局 1.先重置默认样式 找到styles/common.less文件,没有就新建 // 重置默认样式 * {margin: 0;padding: 0;box-sizing: border-box;}// 文字溢出省略号.text-ellipsis-2 {overflow: hidden;-webkit-line-clamp: 2;text-overflow: ellipsis;display: -webkit-box;-…

CentOS7安装RabbitMQ-3.13.7、修改端口号

本文安装版本&#xff1a; Erlang&#xff1a;26.0 官网下载地址 Erlang RabbitMQ&#xff1a;3.13.7 官网下载地址 RabbitMQ RabbitMQ和Erlang对应关系查看&#xff1a;https://www.rabbitmq.com/which-erlang.html 注&#xff1a;安装erlang之前先安装下依赖文件&#xff0…

无人机之放电速率篇

无人机的放电速率是指电池在一定时间内放出其储存电能的能力&#xff0c;这一参数对无人机的飞行时间、性能以及安全性都有重要影响。 一、放电速率的表示方法 放电速率通常用C数来表示。C数越大&#xff0c;表示放电速率越快。例如&#xff0c;一个2C的电池可以在1/2小时内放…

《知道做到》

整体看内容的信息密度较低。绿灯思维、积极心态、反复练习值得借鉴。 引言 行动是老子&#xff0c;知识是儿子&#xff0c;创造是孙子&#xff01;行是知之始&#xff0c;知是行之成。 前言 工作中最让你失望的事情是什么&#xff1f; 一个人行为的改变总是先从内心想法的转…

MySQL 【日期】函数大全(六)

目录 1、TIME_FORMAT() 按照指定的格式格式化时间。 2、TIME_TO_SEC() 将指定的时间值转为秒数。 3、TIMEDIFF() 返回两个时间之间的差值。 4、TIMESTAMP() 累加所有参数并将结果作为日期时间值返回。 5、TIMESTAMPADD() 将指定的时间间隔加到一个日期时间值上并返回结果。…

数据库->库的操作

目录 一、查看数据库 1.显示所有的数据库 二、创建数据库 1.创建数据库 2.查看警告信息 3.创建一个名为database的数据库 三、字符集编码和校验(排序)规则 1.查看数据库⽀持的字符集编码 2.查看数据库⽀持的排序规则 3.一条完整创建库的语句 4. 不同的字串集与排序规…

keepalived(高可用)+nginx(负载均衡)+web

环境 注意&#xff1a; (1) 做高可用负载均衡至少需要四台服务器&#xff1a;两台独立的高可用负载均衡器&#xff0c;两台web服务器做集群 (2) vip&#xff08;虚拟ip&#xff09;不能和物理ip冲突 (3) vip&#xff08;虚拟ip&#xff09;最好设置成和内网ip同一网段&#xf…

传感器驱动系列之PAW3212DB鼠标光电传感器

目录 一、PAW3212DB鼠标光电传感器简介 1.1 主要特点 1.2 引脚定义 1.3 传感器组装 1.4 应用场景 1.5 传感器使用注意 1.5.1 供电选择 1.5.2 SPI读写设置 1.5.3 MOTION引脚 1.6 寄存器说明 1.6.1 Product_ID1寄存器 1.6.2 MOTION_Status寄存器 1.6.3 Delta_X寄存器…

【论文笔记】X-Former: Unifying Contrastive and Reconstruction Learning for MLLMs

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: X-Former: Unifying Contr…

为您的 WordPress 网站打造完美广告布局 A5广告单元格插件

一个为 WordPress 网站量身定制的强大工具,它将彻底改变您展示广告的方式 灵活多变的布局设计 A5 广告单元格插件的核心优势在于其无与伦比的灵活性。无论您是想要创建整齐的网格布局,还是希望打造独特的不规则设计,这款插件都能满足您的需求。 自定义网格数量&#xff1a;从 2…

C# 条形码、二维码标签打印程序

1、条码标答打印主界面 2、打印设置 3、生成QR代码 private void GetBarcode_T(string lr) { QRCodeEncoder qrCodeEncoder = new QRCodeEncoder();//创建一个对象 qrCodeEncoder.QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE; //设置编码测量…

Mamba学习笔记(2)—序列数据处理基础

文章目录 (1) RNN&#xff08;Recurrent Neural Networks&#xff09;基本原理代码定义 (2) SLTM (Long Short-Term Memory)基本原理代码定义 (3) GRU (Gated Recurrent Unit)基本原理代码定义 (4) Transformer&#xff08;☆☆☆Attention Is All You Need☆☆☆&#xff09;0…

量子门电路开销——T门、clifford门、toffoli门、fredkin门

在量子计算中&#xff0c;T门的成本比Clifford门高出很多倍的原因与量子计算中纠错的实现、物理门操作的复杂性以及容错量子计算架构中的成本评估有关。以下是几个关键原因&#xff0c;解释了为什么 T 门的成本在量子计算中远远高于 Clifford 门&#xff1a; 1. T 门和 Cliffo…

递归、搜索与回溯(二)——递归练习与快速幂

文章目录 递归、搜索与回溯——递归两两交换链表中的节点Pow(x, n) 递归、搜索与回溯——递归 该文仍然是解决递归问题&#xff0c;值得注意的是快速幂算法。接下来会系统学习二叉树深搜题目&#xff0c;慢慢走向搜索与回溯。 两两交换链表中的节点 原题链接&#xff1a;24. 两…

AI识谱——将乐曲转化为五线谱

导言&#xff1a; 会乐曲的小伙伴在听到一首好听的乐曲的时候&#xff0c;肯定想过将这首歌曲转换为谱子给弹出来。除了上网找乐谱、请大神帮忙扒谱或者自己扒谱外&#xff0c;小伙伴也可以尝试一下本文介绍的AI识谱流程&#xff0c;让我们开始吧&#xff01; 注意了&#xf…

2024 Python3.10 系统入门+进阶(十七):面向对象基础

目录 一、面向对象概述1.1 面向对象简介1.2 对象和类1.3 定义属性和行为1.3.1 用数据描述对象的状态1.3.2 行为就是动作 1.4 隐藏细节并创建公共接口1.5 组合1.6 继承1.6.1 继承提供抽象1.6.2 多重继承 二、封装2.1 Python类定义2.2 创建类的成员2.2.1 创建实例方法并访问2.2.2…

PythonExcel批量pingIP地址

问题&#xff1a; 作为一个电气工程师&#xff08;PLC&#xff09;&#xff0c;当设备掉线的时候&#xff0c;需要用ping工具来检查网线物理层是否可靠连接&#xff0c;当项目体量过大时&#xff0c;就不能一个手动输入命令了。 解决方案一&#xff1a; 使用CMD命令 for /L %…

机器学习在聚合物及其复合材料中的应用与实践

在当前的工业和科研领域&#xff0c;聚合物及其复合材料因其卓越的物理和化学性能而受到广泛关注。这些材料在航空航天、汽车制造、能源开发和生物医学等多个行业中发挥着至关重要的作用。随着材料科学的发展&#xff0c;传统的实验和理论分析方法已逐渐无法满足新材料研发的需…

【力扣打卡系列】滑动窗口与双指针(无重复字符的最长子串)

坚持按题型打卡&刷&梳理力扣算法题系列&#xff0c;语言为go&#xff0c;Day7 无重复字符的最长子串 题目描述解题思路 不含重复字符——》考虑使用哈希表来存储记录为了提高效率也可以用数组&#xff0c;hash : [128]bool{} &#xff08;因为存的是字符的ASCLL码&…

【Unity踩坑】无法关闭Unity(Application.Shutdown.CleanupEngine)

安装了Unity 6正式版&#xff0c;在关闭Unity 项目时&#xff0c;会出现下面的提示&#xff0c;一直无法关闭。 一直显示 Application.Shutdown.CleanupEngine。 查了一下。这是一个历史性问题了&#xff0c;看来依然没有解决。 参考&#xff1a;Application.Shutdown.Cleanu…