【Linux】线程控制

article/2025/6/29 18:29:34

📝前言:
这篇文章我们来讲讲Linux——线程控制

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏


目录

  • 一,使用介绍
    • 1. POSIX线程库
    • 2. 创建线程
    • 3. 结束线程
    • 4. 等待线程
    • 5. 分离线程
  • 二,使用示例
  • 三,用户层与内核层的“线程”
    • 1. 原码解析
    • 2. 线程栈
    • 3. 线程局部存储
  • 四,模拟封装线程库
    • mythread.hpp
    • Main.cpp
    • 运行效果

一,使用介绍

1. POSIX线程库

  • 对于Linux内核而言,没有线程,只有轻量级进程。但是对于用户而言,需要有线程。
  • 所以,pthread(用户态库)就对Linux的轻量级进程进行了封装,为用户提供线程接口(而把Linux内部的轻量级进程隐藏起来)
  • 头文件:<pthread.h>
  • 编译时要连接对应的库:-lpthread(但是:现合并到libc,不用显式链接也能跑)

某些语言自己的线程库,本质上都是对OS的线程库操作的封装。如,C++的线程库,他会封装Linux的线程库pthread,也会封装Windows的线程库,然后再根据自己的运行环境,通过条件编译选择对应的实现版本。

2. 创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
  • 功能:创建一个新线程,让新线程执行对应的函数
  • 参数
    • thread:返回线程ID(这是属于语言层的线程ID,内核并不认识)
    • attr:设置线程的属性,为NULL表示使用默认属性
    • start_routine:函数地址,线程启动后要执行的函数
    • arg:传给线程启动函数的参数
  • 返回值:
    • 成功返回0
    • 失败返回错误码(pthreads函数出错时不会设置全局变量errno

3. 结束线程

结束进程有三种方法:

  • return:最推荐(不能return局部变量)
  • pthread_exit(void *value_ptr):终止自己,不会影响其他线程(等效于return
    • value_ptr返回值,不能是局部变量(这和后续获取返回值有关,下文会讲述如何获取返回值)
  • pthread_ cancel(pthread_t thread):取消其他线程(注意,只能取消已经启动的线程)
  • 注意:不能使用exit():该函数为整个进程退出

4. 等待线程

和进程一样,主线程需要等待线程(其实目的也一样,后续会讲到对应的结构更好理解)

int pthread_join(pthread_t thread, void **value_ptr);
  • 功能:等待线程(该等待只有阻塞等待)
  • 参数
    • thread:线程ID
    • value_ptr:它指向⼀个指针,后者指向线程的返回值
  • 返回值:
    • 成功返回0
    • 失败返回错误码

5. 分离线程

默认情况下,新创建的线程是joinable的(即:需要等待),如果我们不关心线程的退出信息,可以进行分离线程,让线程退出时,自己释放资源。

int pthread_detach(pthread_t thread)
  • 功能,分离线程(可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离)
  • 参数
    • thread:线程ID
  • 分离以后再join就会出错

二,使用示例

#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>using namespace std;
void *thread(void *num) // 要求返回值和参数类型都是 void*
{cout << "我是线程 " << *((int *)num) << " 我的ID是: " << pthread_self() << endl;sleep(5);cout << "线程 " << *((int *)num) << " 退出" << endl;return num;
}int main()
{pthread_t threads_id[5]; for (int i = 1; i <= 5; i++){pthread_t thread_id;pthread_create(&threads_id[i - 1], nullptr, thread, (void *)&i);pthread_detach(threads_id[i - 1]); // 线程分离sleep(2);}void* ret = nullptr;// 一个个等// for(int i = 0; i <= 4; i++)// {//     pthread_join(threads_id[i], &ret);//     cout << "等到子线程" << *(int*)ret << endl;// }cout << "所有子线程都等到了,主线程退出" << endl;return 0;
}

通过:ps -aL我们可以看到线程的状态
展示部分(一边创建的时候,可能会有一边退出的)
在这里插入图片描述
LWP:轻量级进程的编号,CPU的调度单位(内核层用来标识轻量级进程)

三,用户层与内核层的“线程”

  • 对于Linux,线程是用户态的概念,通过封装Linux的轻量化进程到pthread库实现
  • pthread库底层通过封装系统调用clone来与内核交互

首先,pthread是一个库,也是一个文件,它会通过mmap映射到进程地址空间上。
在这里插入图片描述

其次,库中每个线程有TCB(语言层),并用链表或者其他数据结构组织。(类似PCB)
在这个TCB里,主要有三个重要的结构:

  1. struct pthread:(线程控制块的核心)其中包括:用户层属性 + 内核映射 …
  2. 线程局部存储:用来存储线程希望“专有”的变量(只能存储内置类型和部分指针)
  3. 线程独立的栈结构:存放该线程执行时产生的临时数据
    在这里插入图片描述

1. 原码解析

glibc-2.4pthread源码相关内容(只看重点):

// 线程的属性(用 struct pthread_attr 记录)
const struct pthread_attr *iattr = (struct pthread_attr *)attr;// 指向struct pthread的起始地址(虚拟的)
// 这玩意就是我们调用pthread_creat 得到的线程ID
struct pthread *pd = NULL;// 根据 iattr 中的栈属性(如 stacksize)分配线程栈
// 同时分配并初始化 struct pthread 结构体
// pd 存储返回新分配的 TCB 地址(同时也就是 struct pthread的起始地址)
int err = ALLOCATE_STACK(iattr, &pd);// 记录线程要执行的函数的入口和参数
pd->start_routine = start_routine;
pd->arg = arg;// 把线程ID存入newthread,newthread指向线程的 TCB
*newthread = (pthread_t)pd;// 检查是否分离
bool is_detached = IS_DETACHED(pd);// 创建一个进程,内部封装 clone 
err = create_thread(pd, iattr, STACK_VARIABLES_ARGS);

struct pthread_attr

  • 用于用户在创建线程前配置线程属性的结构体(如栈大小、分离状态等)
  • 属于用户空间的 API,用户可以直接操作
  • 只在调用 pthread_create() 时起作用

关键属性:

...
int flags; // 存储线程的各种属性标志位,其中包括分离状态/* Stack handling. */
void *stackaddr; // 栈的起始地址
size_t stacksize; // 栈的大小
...

struct pthread
TCB的核心,(类似文件的FILE结构体)

// 线程的 LWP(CPU的调度单位)
pid_t tid;
// 线程所属的进程的PID
pid_t pid;// 存放进程函数退出的返回值
void *result;// 用户指定的线程函数入口和参数
void *(*start_routine) (void *);
void *arg;// 线程自己的栈和⼤⼩
void *stackblock; // 指向栈
size_t stackblock_size;
  • 当我们创建一个线程,线程通过用户提供的函数入口start_routine去执行对应的代码
  • 产生的临时数据存放在用户提供的独立的栈中
  • 线程运行完毕,返回值就是void*, 返回值会被拷贝到result
  • 线程退出后,结构体本身和线程栈不会立即释放
  • 所以我们要用 pthread_join 等待,并且 join 获取线程退出信息时,就是读取该结构体

create_thread

// 封装的clone
...
int res = do_clone(pd, attr, clone_flags, start_thread,
STACK_VARIABLES_ARGS, stopped);

do_clone

...
if (ARCH_CLONE(fct, STACK_VARIABLES_ARGS, clone_flags,
pd, &pd->tid, TLS_VALUE, &pd->tid) == -1)

ARCH_CLONE __clone

...
movl $SYS_ify(clone),%eax // 获取系统调⽤号
...
syscall // 陷⼊内核(x86_32是int 80),内核创建轻量级进程
...
  • 所以,在创建线程的时候,其实就是在pthread库内部,创建好描述线程的结构体对象struct pthread,填充属性(用户层)
    • struct pthread通常在线程栈的顶部高地址端(也就是在TCB的前面)
  • 然后由系统创建好线程栈(通过 mmap分配)
  • 调用clone,让内核创建轻量级进程,并传入回调函数和参数(系统层)
  • 其实,库提供的无非就是未来操作线程的API,通过属性设置线程的优先级之类,而真正调度的过程,还是内核来的

2. 线程栈

  • 我们在传递线程栈的起始地址的时候,传递的是高地址,因为线程栈在主进程的堆上开辟,堆向下增长
  • 线程栈的空间创建好以后就是固定的,大小为页大小的整数倍
  • 它其实是在进程的地址空间中map出来的⼀块内存区域

3. 线程局部存储

  • 如果我们定义一个全局变量,然后有两个线程,线程a对变量进行修改,线程b读取,则b是能读到a的修改的。(此时变量是存储在进程的已初始化数据区的)
  • 但是如果我们在变量加__thread,则可以引导编译器:把变量的存储位置改到线程的局部存储区。
  • 这时候,a对变量的修改,b就看不到(虽然两进程访问的变量名相同,但是访问的实际是不同的虚拟地址)
  • 注意:线程局部存储只能存储内置类型和部分指针

四,模拟封装线程库

mythread.hpp

#pragma once
#include <iostream>
#include <pthread.h>
#include <functional>
#include <stdlib.h>
#include <string>namespace tr
{int cnt = 1; // 设计一个计数器class Mythread{private:static void *start_routine(void *obj) // obj 是 this指针{// 类内成员函数有this指针,无法传入pthread_create// 所以设计static成员函数(接受this),来回调要传入的方法Mythread *self = static_cast<Mythread *>(obj);// 在这里往线程局部存储存入_name,以便在类外能够获取pthread_setname_np(self->_tid, self->_name.c_str());self->_func(); // 回调return nullptr;}public:using func_t = std::function<void()>; // 接受一个无参无返回值的函数的回调Mythread(bool enablejoin, func_t func) // 构造函数是在主线程执行的: _enablejoin(enablejoin), _running(true), _func(func){int ret = pthread_create(&_tid, nullptr, start_routine, this);if (ret != 0){perror("pthread_create");exit(EXIT_FAILURE);}else{_name = "thread " + std::to_string(cnt++);std::cout << _name << "创建成功" << std::endl;}if (!_enablejoin){pthread_detach(_tid);}}void Detach(){if (_enablejoin){pthread_detach(_tid);}_enablejoin = false;}void Cancel(){if (_running){pthread_cancel(_tid);}_running = false;_enablejoin = false;}void Join() // 只能 join 自己{if (_enablejoin){int ret = pthread_join(_tid, nullptr);if (ret != 0){perror("pthread_join");exit(EXIT_FAILURE);}else{std::cout << _name << "被成功join" << std::endl;}}else{std::cout << _name << "已经分离, 不能被join" << std::endl;}}~Mythread(){}private:pthread_t _tid; // 用户线程 IDbool _enablejoin;bool _running;std::string _name;func_t _func; // 该线程要执行的函数};
}

Main.cpp

#include "MyThread.hpp"
#include <unistd.h>
void func1()
{sleep(1); // 等一下名字设置char name[256];pthread_getname_np(pthread_self(), name, sizeof(name));std::cout << name << "任务执行完毕" << std::endl;
}void func2()
{sleep(1);char name[256];pthread_getname_np(pthread_self(), name, sizeof(name));std::cout << name << "任务执行完毕" << std::endl;
}int main()
{pthread_setname_np(pthread_self(), "main_thread");tr::Mythread t1(true, func1);tr::Mythread t2(true, func2);// t1.Detach();// t1.Cancel();t1.Join();t2.Join();sleep(3); // 让主线程慢一点退出std::cout << "执行完毕" << std::endl;return 0;
}

运行效果

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


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!


http://www.hkcw.cn/article/wcIOGCCLPQ.shtml

相关文章

CppCon 2014 学习:Hardening Your Code

“Hardening Your Code” 是指增强代码的健壮性、安全性、可维护性和可测试性&#xff0c;确保在各种边界条件、错误场景甚至恶意输入下&#xff0c;代码依然稳定运行&#xff0c;不崩溃、不泄露资源&#xff0c;也不产生未定义行为。 什么是“Hardening Your Code”&#xff…

【js逆向_AES】某专业技术人员继续教育平台登录分析及模拟实践

目标&#xff1a;account&#xff0c;password加密 网址&#xff1a;aHR0cHM6Ly93d3cuZ3N6eGp5cHguY24vd2ViL2luZGV4 请求载荷加密方式 账号加密&#xff1a; 网页调试输出&#xff1a; python代码&#xff1a; from Cryptodome.Cipher import AES import base64 from Crypto…

《信号与系统》--期末总结V1.0

《信号与系统》–期末总结V1.0 学习链接 入门&#xff1a;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;_哔哩哔哩_bilibili 精通&#xff1a;2022浙江大学信号与系统&#xff08;含配…

可视化大屏通用模板Axure原型设计案例

本文将介绍一款基于Axure设计的可视化大屏通用模板&#xff0c;适用于城市、网络安全、园区、交通、社区、工业、医疗、能源等多个领域。 模板概述 这款Axure可视化大屏通用模板集成了多种数据展示模块和组件&#xff0c;旨在为用户提供一个灵活、可定制的数据展示平台。无论…

AI来敲门:我们该如何与焦虑共舞

最近一份覆盖国内上万职场人的调研报告像一颗深水炸弹&#xff0c;在职场圈激起层层涟漪——85.53%的人担心AI会抢走自己的饭碗&#xff0c;67.57%的人认为这会在五年内发生。更令人意外的是&#xff0c;这些焦虑的职场人中&#xff0c;高达34.13%出现了抑郁症状&#xff0c;这…

单调栈(打卡)

本篇基于b站灵茶山艾府。 下面是灵神上课讲解的题目与课后作业&#xff0c;课后作业还有三道实在写不下去了&#xff0c;下次再写。 739. 每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是…

【C语言入门级教学】冒泡排序和指针数组

文章目录 1.冒泡排序2.⼆级指针3.指针数组4.指针数组模拟⼆维数组 1.冒泡排序 冒泡排序的核⼼思想&#xff1a;两两相邻的元素进⾏⽐较。 //⽅法1 void bubble_sort(int arr[], int sz)//参数接收数组元素个数 { int i 0;for(i0; i-1; i) { int j 0; for(j0; j-1; j) { …

源码解析(三):Stable Diffusion

原文 技术博客 &#x1f600; Stable Diffusion是一种基于扩散模型&#xff08;Diffusion Model&#xff09;的生成式AI技术&#xff0c;通过逐步去噪过程将随机噪声转化为高质量图像。其核心优势在于开源免费、支持本地部署&#xff0c;且能通过文本提示&#xff08;prompt&am…

洛雪音乐+多种音源同步更新,附带安装教程 -【PC端/安卓端】音乐软件

今天&#xff0c;就为大家介绍一款全网免费听歌神器——‌洛雪音乐‌&#xff01; &#x1f3b6; 洛雪音乐&#xff1a;&#xff08;文末获取软件&#xff09; 一、软件亮点 全平台支持‌&#xff1a;无论是Windows系统还是安卓手机&#xff0c;洛雪音乐都能随时伴你左右&am…

【CATIA的二次开发18】根对象Application涉及用户交互相关方法

在CATIA VBA开发中&#xff0c;对根对象Application涉及用户交互相关方法进行详细总结&#xff0c;并且用不同形式展示出来。供大家后续开发全面了解Application对象的方法&#xff0c;以便在开发过程中快速查找和使用&#xff1a; 一、Application常用方法分类 1、基础控制与…

密码学:解析Feistel网络结构及实现代码

概述 Feistel网络是由IBM密码学家Horst Feistel在20世纪70年代提出的对称加密结构&#xff0c;已成为现代分组密码的核心框架。DES、Blowfish、RC5等经典加密算法均基于此结构。其核心思想是将输入明文分组分成左右两半&#xff0c;通过多轮迭代操作实现加密&#xff0c;每轮使…

JavaSE知识总结(集合篇) ~个人笔记以及不断思考~持续更新

目录 集合 List List的各种接口API List的五种遍历方式 List的删除是内部是怎么做的&#xff1f; ArrayList和LinkedList的区别 Vetor和Stack是什么&#xff1f; Set Set的特点 HashSet TreeSet LinkedHashSet Map HashMap LinkedHashMap TreeMap 集合 在Java…

Linux中的mysql备份与恢复

一、安装mysql社区服务 二、数据库的介绍 三、备份类型和备份工具 一、安装mysql社区服务 这是小编自己写的&#xff0c;没有安装的去看看 Linux换源以及yum安装nginx和mysql-CSDN博客 二、数据库的介绍 2.1 数据库的组成 数据库是一堆物理文件的集合&#xff0c;主要包括…

也说字母L:柔软的长舌

英语单词 tongue&#xff0c;意为“舌头” tongue n.舌&#xff0c;舌头&#xff1b;语言 很显然&#xff0c;“语言”是引申义&#xff0c;因为语言是抽象的&#xff0c;但舌头是具象的&#xff0c;根据由简入繁的原则&#xff0c;tongue显然首先是象形起义&#xff0c;表达…

【机器学习】决策树

目录 一、引言 二、决策树的构造 三、决策树的ID3算法 四、决策树的C4.5算法 五、决策树的CART算法 六、动手实现决策树C4.5的算法详解步骤以及Python完整代码实现 一、引言 在机器学习中,有一种与神经网络并行的非参数化模型——决策树模型及其变种。顾名思义,决…

美提高钢铝关税至50% 欧盟深表遗憾 谈判进程加速

6月2日,欧盟委员会新闻发言人对美国宣布将钢铁和铝关税从25%提高至50%表示遗憾,认为这一决定加剧了大西洋两岸的经济不确定性。发言人提到谈判仍在继续,双方已同意加快谈判进程,并计划本周举行会谈。欧盟贸易专员塞夫科维奇将于6月4日在法国巴黎会见美国贸易代表格里尔。美…

基于ubuntu和树莓派环境对游戏进行移植

目录 一、在Ubuntu中对波斯王子游戏进行移植 1.1修改Ubuntu系统的仓库镜像网站为国内网站 1.2安装mininim 软件所依赖的库 1.3 编译mininim 软件 二、在树莓派中对波斯王子游戏移植 2.1安装相关环境 2.3编译mininim 软件 三、使用树莓派实现流水灯 一、在Ubuntu中对波…

设计模式——备忘录设计模式(行为型)

摘要 备忘录设计模式是一种行为型设计模式&#xff0c;用于在不破坏封装性的前提下&#xff0c;捕获对象的内部状态并在需要时恢复。它包含三个关键角色&#xff1a;原发器&#xff08;Originator&#xff09;、备忘录&#xff08;Memento&#xff09;和负责人&#xff08;Car…

Linux磁盘管理

磁盘基础 分类 运行方式与原理 详细信息 机械硬盘(HDD)-家用 电机带动磁盘高速旋转&#xff0c;读取数据&#xff1b;速度可以达到5400&#xff0c;7200 rpm&#xff08;round per minute-转/分钟&#xff09; 固态硬盘&#xff08;SSD) 集成电路与芯片&#xff0c;存储芯…

C# XAML 基础:构建现代 Windows 应用程序的 UI 语言

在现代 Windows 应用程序开发中&#xff0c;XAML (eXtensible Application Markup Language) 扮演着至关重要的角色。作为一种基于 XML 的声明性语言&#xff0c;XAML 为 WPF (Windows Presentation Foundation)、UWP (Universal Windows Platform) 和 Xamarin.Forms 应用程序提…