当前位置: 首页 > news >正文

【Liunx专栏_6】Linux线程概念与控制

在这里插入图片描述

目录

  • 1、线程是什么?通过一个图来理解……
  • 2、Linux进程和线程?
    • 2.1、之间的关系和区别
    • 2.2、线程的优缺点?
  • 3、线程的创建
    • 3.1、POSIX线程库
    • 3.2、创建线程
    • 3.3、PS查看运行的线程
  • 4、线程的终止
  • 5、线程的等待
  • 6、线程分离
  • 7、线程封装

1、线程是什么?通过一个图来理解……

首先,我们知道进程等于PCB+自己的数据和代码,在创建进程的时候,操作系统要对进程进行描述,则创建一个结构体,里面包含进程的所有属性信息(标志符、状态信息、优先级等……),该结构体在Linux中称为“PCB”,即进程控制块,在Litnux中结构体名叫task_strcut。该信息会存储在程序地址空间中,也就是所说的虚拟地址空间。里面就存储了该进程的所有信息和自己的代码和数据信息。它们通过页表进行映射到物理内存空间,也就是加载内存,进行CPU的调度执行。
上面描述的整个流程,从进程的创建(PCB的创建)、程序的代码和数据通过页表进行的映射关系的建立、到CPU的调度执行,该流程就可以看做是一个执行路线,只不过只有一个执行流,我么把该执行线路就称之为线程
即线程就是一个进程中的一天控制序列。并且一个进程中至少有一个执行线程。
在这里插入图片描述
之前描述的PCB只有一个,此处就称为该进程有一个执行线程,但在一个进程中可以存在多个执行流,所有执行流会公用一个虚拟内存空间,在虚拟内存中,操作系统会将资源合理分配给多个执行流。

2、Linux进程和线程?

2.1、之间的关系和区别

  1. 进程:是系统资源分配的基本单位。
  2. 线程:是CPU调度执行的基本单位。
  3. 线程共享进程数据的同时,线程也有自己的一部分数据信息,用来描述不同线程。(线程ID、调度优先级、信号屏蔽字等……)
  4. 进程中的多线程共享:即在一个进程中,有一个程序地址空间,该空间被所有线程共享,因此Text Segment、Data Segment都是共享的,因此只要定义一个函数后,所有线程都是可以调用的,同时定义了一个全局变量后也是共享的,此外多线程之间还共享了文件描述符表、当前工作目录、用户id和组id等……
  5. 线程是进程的执行分支,只要一个线程出现异常情况,也就影响到整个进程,从而导致整个进程的崩溃,进而终止退出。

2.2、线程的优缺点?

  1. 线程的优点:

从创建的角度看:创建一个新线程比创建一个新进程的代价小的多,因为从上面的关系和区别可以看出,创建一个线程不需要从新分配虚拟地空间,没有数据的大量拷贝,而创建进程需要创建新的程序地址空间,同时还要拷贝原始数据,增加的系统的消耗。

从切换的角度看:与进程间切换相比,线程间切换需要操作的工作量更小,主要区别就是线程之间的切换,虚拟地址空间是相同的。
从执行效率上看:在多线程的情况下可以实现并发执行,提高执行的效率。

  1. 线程的缺点:

主要是性能上,一个处理器上有密集型线程的数量进行执行的时候,会有较大的性能损失,会增加额外的同步和调度开销。但是,合理的使⽤多线程,能提⾼CPU密集型程序的执⾏效率。

健壮性上:在多线程的情况下,由于虚拟地址空间是共享的,会造成一些空间的数据在同一时刻被多个线程访问,即缺少资源的保护,因此在后面会提到互斥与信号量,用来应对多线程的情况下共享资源的多次访问。

3、线程的创建

3.1、POSIX线程库

  • 在Linux中,创建线程就会用到里面的库,需要包含头文件<pthread.h>。
  • 同时在链接这些线程函数库的时候,编译时需要加-lpthread选项。

3.2、创建线程

  1. 函数接口是:pthread_create()
    在这里插入图片描述
  2. 在代码中的使用:
#include<iostream>
#include<pthread.h>
#include<string.h>
#include<unistd.h>void *work(void *arg)
{while(1){std::cout<<"我是线程-1: "<<pthread_self()<<"……"<<std::endl;sleep(1);}
}int main()
{pthread_t tid;int ret=pthread_create(&tid,nullptr,work,nullptr);if(ret!=0){std::cout<<"create failed!,code_num:"<<strerror(ret)<<std::endl;exit(-1);}while(1){std::cout<<"我是主线程: "<<pthread_self()<<"……"<<std::endl;sleep(1);}return 0;
}

在这里插入图片描述

  • 上面的代码就是在主线程中创建一个新的线程,总共两个线程,因此运行后有两个执行流,整个程序称为一个进程,每个线程有自己的唯一表示符号,上面使⽤是是 pthread_self(),得到的这个数实际上是⼀个地址,在虚拟地址空间上的⼀个地址,通过这个地址, 可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。即线程id。我们通过打印看到确实有两个同时输出在显示器上,下面通过ps查看是不是真有两个线程在运行。

3.3、PS查看运行的线程

while :; do ps -aL | head -1 && ps -aL | grep 可执行程序名 | grep -v grep ; echo "************" ; sleep 1 ; done循环监控查看线程情况。
在这里插入图片描述
通过布局监控,运行程序可以看到,确实有两个线程,它们的PID都是一样的,说明这两个线程拥有同一个父进程,看到LWP,其中一个和PID相同,说明该线程就是主线程,另一个就是创建的新线程。LWP 是什么呢?LWP 得到的是真正的线程ID

注意:
主线程的栈在虚拟地址空间的栈上,⽽其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库提供给我们的。⽽pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。

4、线程的终止

只终止其中的某个线程,而不是终止整个进程,有一下三种方法:

  1. 在创建的线程函数中调用return,注意不要在主线程中调用return,在主线程中调用retrun ,就相当于调用exit
void *work(void *arg)
{int num=5;while(num--){std::cout<<"我是线程-1: "<<pthread_self()<<"……"<<std::endl;sleep(1);}return nullptr;
}

在这里插入图片描述

  1. 直接调用退出接口pthread_exit()终止线程。

在这里插入图片描述
value_ptr:value_ptr不要指向⼀个局部变量

  1. 调用pthread_cancel()取消一个在执行的线程

在这里插入图片描述
参数就是传入线程id。成功返回0。

5、线程的等待

  • 为什么要等待?
    因为退出的线程,其空间没有被释放,依旧在进程地址空间中的。若创建新的线程并不会使用刚退出的线程的地址空间,因此就造成了浪费。

  • 等待的函数接口:pthread_join()

在这里插入图片描述
thread:线程ID
value_ptr:它指向⼀个指针,后者指向线程的返回值,返回值会根据调用不同的终止接口返回不同的值。

  • 通过代码演示:创建三个线程,调用不同的终止接口,看等待的返回接收参数值是什么……
#include <iostream>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void *thread1(void *arg)
{std::cout << "thread1 running……" << std::endl;int *p = (int *)malloc(sizeof(int));*p = 1;return (void *)p;
}void *thread2(void *arg)
{std::cout << "thread2 running……" << std::endl;int *p = (int *)malloc(sizeof(int));*p = 2;pthread_exit((void *)p);
}void *thread3(void *arg)
{while (1){std::cout << "thread3 running……" << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;void *ret;if (pthread_create(&tid, nullptr, thread1, nullptr) != 0){std::cout << "创建失败……" << std::endl;exit(-1);}pthread_join(tid, &ret);printf("thread1 退出,thread1 id=%X,return code:%d\n", tid, *(int *)ret);free(ret);if (pthread_create(&tid, nullptr, thread2, nullptr) != 0){std::cout << "创建失败……" << std::endl;exit(-1);}pthread_join(tid, &ret);printf("thread2 退出,thread2 id=%X,return code:%d\n", tid, *(int *)ret);free(ret);if (pthread_create(&tid, nullptr, thread3, nullptr) != 0){std::cout << "创建失败……" << std::endl;exit(-1);}sleep(3);pthread_cancel(tid);pthread_join(tid, &ret);if (ret == PTHREAD_CANCELED)printf("thread return, thread id=%X, return code:PTHREAD_CANCELED\n",tid);elseprintf("thread return, thread id=%X, return code:NULL\n", tid);while(1){std::cout<<"我是主线程……"<<std::endl;sleep(1);}return 0;
}

在这里插入图片描述

  1. 如果thread线程通过return返回,value_ ptr所指向的单元⾥存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调⽤pthread_ cancel异常终掉,value_ ptr所指向的单元⾥存放的是常数PTHREAD_ CANCELED。
  3. 如果thread线程是⾃⼰调⽤pthread_exit终⽌的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终⽌状态不感兴趣,可以传NULL给value_ ptr参数。

6、线程分离

上面看到线程的等待,线程退出后需要对线程进行等待,保证资源的释放,避免系统资源的泄露。可以看到通过等待线程进行资源的释放不是太方便,因此线程创建在默认情况下是joinable的,也就是可以线程分离的,指将一个线程从其创建者(通常是主线程)中分离出来,使其成为一个独立的执行实体。分离后的线程在终止时会自动释放其资源,而不需要其他线程显式地等待或回收它。

主要特点
1、资源自动回收:分离的线程在结束时系统会自动回收其资源
2、无需join:其他线程不需要调用join()或类似函数来等待分离线程结束
3、独立性:分离后的线程运行独立于创建它的线程

线程分离的接口函数:pthread_detach(线程ID)
下面代码描述:即通过线程分离技术,让线程自己执行5次后自动分离,自动释放资源,分离后依然显示调用join等到,依旧会等待的,只不过不会执行后面的代码,因为是阻塞式等待的。

#include <iostream>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* thread1(void* arg)
{int num=5;while(num--){std::cout<<(char*)arg<<std::endl;sleep(1);}pthread_detach(pthread_self());return nullptr;
}int main()
{pthread_t tid;if(pthread_create(&tid,nullptr,thread1,(void*)"thread1线程running……")!=0){std::cout<<"创建线程失败……"<<std::endl;}sleep(1);if(pthread_join(tid,nullptr)==0){std::cout<<"等待成功……"<<std::endl;return 0;//不return 就会继续执行后面的主线程}else{std::cout<<"等待失败……"<<std::endl;return -1;}while(1){std::cout<<"主线程运行中……"<<std::endl;sleep(1);}return 0;
}

使用场景
1、后台任务(如日志记录、监控)
2、不需要与主线程同步的一次性任务
3、长时间运行的服务线程

注意事项
1、分离后无法再join该线程
2、分离线程不能返回结果给创建者线程
3、主线程退出可能导致分离线程被强制终止(取决于平台和设置)
4、需要谨慎处理共享资源,因为缺乏同步机制

7、线程封装

下面对创建线程步骤做一次封装:

#pragma once#include <iostream>
#include <cstring>
#include <functional>
#include <pthread.h>namespace ThreadMoodule
{using work_t = std::function<void(std::string)>;static int NameId = 1;// 枚举线程状态enum STATUS{NEW,RUNNING,STOP};// 封装线程class Thread{private:// 此处需要注意,写为成员函数的时候,函数的第一个参数默认是this*,若下面线程函数就需要用static修改,或者写在类外,由于封装,因此加staticstatic void *Routine(void *args){Thread *td = static_cast<Thread *>(args);td->_task(td->_name);return 0;}void EnableDetach(){_joinable=false;}public:Thread(work_t task): _task(task), _status(STATUS::NEW), _joinable(true){_name = "thread_" + std::to_string(NameId++);}bool StartThread(){// 启动线程if (_status != STATUS::RUNNING){// int n = pthread_create(&_tid, nullptr, Routine, nullptr);//第四个参数由于线程函数写在类中,// 被static修饰无法使用this*指针,因此无法访问成员变量,因此该参数传入this,传递给函数。int n = pthread_create(&_tid, nullptr, Routine, this);if (n != 0){return false; // 创建失败}_status = STATUS::RUNNING;return true;}return false;}bool StopThread() // 即取消{if (_status == STATUS::RUNNING){int n = pthread_cancel(_tid);if (n != 0){return false;}_status = STATUS::STOP;return true;}return false;}bool JoinThread(){if (_joinable){int n = pthread_join(_tid, nullptr);if (n != 0){return false;}_status = STATUS::STOP;return true;}return false;}void DetachThread(){//前提是没有分离的EnableDetach();pthread_detach(_tid);}std::string Name(){return _name;}bool IsJoinAble(){return _joinable;}~Thread(){}private:std::string _name;pthread_t _tid;pid_t _pid;     // 多线程,所有pid都是一样的bool _joinable; // 默认是不可分离的work_t _task;STATUS _status;};
}
#include "Thread.hpp"
#include <unistd.h>
#include <vector>#define THREAD_NUM 5int main()
{std::vector<ThreadMoodule::Thread> threads;for (int i = 0; i < THREAD_NUM; i++){ThreadMoodule::Thread t([](std::string name){while(true){std::cout<<name<<"执行任务……"<<std::endl;sleep(1);} });threads.emplace_back(t);}for(auto& n:threads){n.StartThread();}sleep(1);for(auto& e:threads){e.StopThread();}sleep(1);for(auto& e:threads){e.JoinThread();}return 0;
}

相关文章:

  • GitFlow 工作模式(详解)
  • Continue 开源 AI 编程助手框架深度分析
  • 小记Vert.x的Pipe都做了什么
  • 【GPT模型训练】第二课:张量与秩:从数学本质到深度学习的基础概念解析
  • 自动化立体仓库堆垛机控制系统STEP7 OB1功能块
  • 贝叶斯定理与医学分析(t检验场景)
  • 【证书】2025公益课,人工智能训练师-高级,知识点与题库(橙点同学)
  • Redis持久化策略:RDB与AOF详解
  • 【刷题模板】链表、堆栈
  • 【Vue3】(三)vue3中的pinia状态管理、组件通信
  • 【教学类-53-02】20250607自助餐餐盘教学版(配餐+自助餐)
  • 【razor】x264 在 的intra-refresh和IDR插帧
  • c++对halcon的动态链接库dll封装及调用(细细讲)
  • LLMs 系列科普文(3)
  • 深入探索CDC:实时数据同步利器
  • 227.2018年蓝桥杯国赛 - 交换次数(中等)- 贪心
  • 手动实现C#ArrayList容器
  • yaklang 中的各种 fuzztag 标签及其用法
  • SOC-ESP32S3部分:36-适配自己的板卡
  • 【python深度学习】Day 48 PyTorch基本数据类型与操作
  • dreamweaver的优点/抖音seo排名软件哪个好
  • 做网站开发的有外快嘛/磁力猫引擎入口
  • 网站翻页代码/桂林市天气预报
  • php公司网站/明天上海封控16个区
  • 西安市规划建设局网站/爱站网关键词挖掘查询
  • 网站免费空间哪里申请/如何发布自己的广告