【Linux】线程控制

【Linux】线程控制

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

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

目录

一,使用介绍

1. POSIX线程库

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

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

2. 创建线程

intpthread_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. 等待线程

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

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

5. 分离线程

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

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

二,使用示例

#include<iostream>#include<string>#include<unistd.h>#include<pthread.h>usingnamespace std;void*thread(void*num)// 要求返回值和参数类型都是 void*{ cout <<"我是线程 "<<*((int*)num)<<" 我的ID是: "<<pthread_self()<< endl;sleep(5); cout <<"线程 "<<*((int*)num)<<" 退出"<< endl;return num;}intmain(){ 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;return0;}

通过:ps -aL我们可以看到线程的状态
展示部分(一边创建的时候,可能会有一边退出的)

在这里插入图片描述


LWP:轻量级进程的编号,CPU的调度单位(内核层用来标识轻量级进程)

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

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

首先,pthread是一个库,也是一个文件,它会通过mmap映射到进程地址空间上。

在这里插入图片描述

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

  1. struct pthread:(线程控制块的核心)其中包括:用户层属性 + 内核映射 …
  2. 线程局部存储:用来存储线程希望“专有”的变量(只能存储内置类型和部分指针)

线程独立的栈结构:存放该线程执行时产生的临时数据

在这里插入图片描述

1. 原码解析

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

// 线程的属性(用 struct pthread_attr 记录)conststructpthread_attr*iattr =(structpthread_attr*)attr;// 指向struct pthread的起始地址(虚拟的)// 这玩意就是我们调用pthread_creat 得到的线程IDstructpthread*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

#pragmaonce#include<iostream>#include<pthread.h>#include<functional>#include<stdlib.h>#include<string>namespace tr {int cnt =1;// 设计一个计数器classMythread{private:staticvoid*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();// 回调returnnullptr;}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);}}voidDetach(){if(_enablejoin){pthread_detach(_tid);} _enablejoin =false;}voidCancel(){if(_running){pthread_cancel(_tid);} _running =false; _enablejoin =false;}voidJoin()// 只能 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>voidfunc1(){sleep(1);// 等一下名字设置char name[256];pthread_getname_np(pthread_self(), name,sizeof(name)); std::cout << name <<"任务执行完毕"<< std::endl;}voidfunc2(){sleep(1);char name[256];pthread_getname_np(pthread_self(), name,sizeof(name)); std::cout << name <<"任务执行完毕"<< std::endl;}intmain(){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;return0;}

运行效果

在这里插入图片描述


在这里插入图片描述

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

Read more

Flutter 三方库 tiktoken 鸿蒙端侧 AI 重载计算环境适配指南:极尽压榨设备级 BPE 分词器吞吐量边界,打造工业级精控的大模型高昂运算成本阀门-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 tiktoken 鸿蒙端侧 AI 重载计算环境适配指南:极尽压榨设备级 BPE 分词器吞吐量边界,打造工业级精控的大模型高昂运算成本阀门-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 tiktoken 鸿蒙端侧 AI 重载计算环境适配指南:极尽压榨设备级 BPE 分词器吞吐量边界,打造工业级精控的大模型高昂运算成本阀门防线 在开发鸿蒙平台的生成式 AI 应用(如大模型助手、智能写作或 Rerank 逻辑)时,如何精确预估 Prompt 的消耗?如何实现窗口精度的截断?tiktoken 提供了一套完整的 OpenAI BPE(字节对编码)分词算法实现。本文将详解该库在 OpenHarmony 上的适配要点。 前言 什么是 tiktoken?它是 OpenAI 为其 GPT 系列模型推出的高性能 BPE 分词器。不同于常规的字符计数,Token 是模型处理文本的最小单位。在鸿蒙操作系统强调的“

By Ne0inhk
AI的提示词专栏:通过 “Few-Shot-in-Context” 进行知识注入

AI的提示词专栏:通过 “Few-Shot-in-Context” 进行知识注入

AI的提示词专栏:通过 “Few-Shot-in-Context” 进行知识注入 本文围绕 “Few-Shot-in-Context” 这一轻量级知识注入方案展开,先阐述其核心价值 —— 无需修改大语言模型(LLM)参数,仅通过 3-5 个示例即可补充模型时效性、专业性知识缺口,对比传统微调成本低、效率高的优势;接着解析技术原理,即模型通过示例解析、模式归纳、任务迁移三步掌握知识逻辑;随后重点提出示例设计五大原则,结合医疗、金融、编程等五大行业实战案例,展示该方案在不同场景的应用;还针对模型复述示例、忽略边界条件等六大常见问题给出解决方案;最后总结核心要点,并展望多模态注入、动态更新等未来方向,为 LLM 个性化行业应用提供路径。 人工智能专栏介绍     人工智能学习合集专栏是 AI 学习者的实用工具。它像一个全面的 AI 知识库,把提示词设计、AI 创作、智能绘图等多个细分领域的知识整合起来。无论你是刚接触 AI 的新手,还是有一定基础想提升的人,都能在这里找到合适的内容。

By Ne0inhk
小白福音!Windows 一键装 OpenClaw,AI 办公从此超简单

小白福音!Windows 一键装 OpenClaw,AI 办公从此超简单

前言 如果你是技术小白,想玩 AI、想用自动化、想实现远程电脑控制,却总被 “命令行、环境、部署、API” 这些词吓到,那 OpenClaw 就是为你量身定做的解决方案。它把复杂的环境配置、模型接入、平台对接全部封装成一键脚本,你只需要点几下、输几行命令,就能在 Windows 上完整搭建 AI 助手。不用理解原理,不用啃技术文档,不用踩坑排查,跟着教程走,30 分钟就能拥有能听会做、能远程访问的 AI 助手。从此写汇报、找文件、做 PPT、处理表格、远程办事,全都交给 AI,办公效率直接起飞。 这篇文章将手把手带你完成 OpenClaw 在 Windows 系统上的部署,即使你是技术小白,也能轻松上手。

By Ne0inhk
从0到1彻底掌握Trae:手把手带你实战开发AI Chatbot,提升开发效率的必备指南!

从0到1彻底掌握Trae:手把手带你实战开发AI Chatbot,提升开发效率的必备指南!

我正在参加Trae「超级体验官」创意实践征文,本文所使用的 Trae 免费下载链接:www.trae.ai/?utm_source… 暴富技巧 比特鹰作为国内领先的 AI+Web3 领域企业,团队充满年轻活力 ——95% 成员为 00 后,不仅技术氛围浓厚,还会为每位成员量身定制成长规划;在职业发展层面,公司前景广阔,提供餐饮补贴、租房补贴、年底奖金、股票期权及额外假期等多重福利,助力员工在 35 岁前实现财富自由 目前公司正招聘海外运营、前端、后端、智能合约、AI 开发、HR 等岗位,有意向者可加微信联系: ai_lianqq 前言 大家好,我是小Q,字节跳动近期推出了一款 AI IDE—— Trae,

By Ne0inhk