【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;
}

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

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

相关文章

「Java基本语法」标识符、关键字与常量

知识点解析 1&#xff0e;标识符&#xff08;Identifiers&#xff09;&#xff1a;用于命名类、方法、变量等。 标识符命名规则&#xff1a; 标识符由字母&#xff08;A-Z&#xff0c;a-z&#xff09;、数字&#xff08;0-9&#xff09;、下划线“_”或美元符号“$”组成。标…

Nginx Stream 层连接数限流实战ngx_stream_limit_conn_module

1.为什么需要连接数限流&#xff1f; 数据库/Redis/MQ 连接耗资源&#xff1a;恶意脚本或误配可能瞬间占满连接池&#xff0c;拖垮后端。防御慢速攻击&#xff1a;层叠式限速&#xff08;连接数&#xff0b;带宽&#xff09;可阻挡「Slow Loris」之类的 TCP 低速洪水。公平接入…

LLMs之Structured Output:vLLM 结构化输出指南—从约束生成到自动解析与高效实现

LLMs之Structured Output&#xff1a;vLLM 结构化输出指南—从约束生成到自动解析与高效实现 导读&#xff1a;随着大语言模型&#xff08;LLM&#xff09;在各类任务中的广泛应用&#xff0c;如何使其输出具备可控性、结构化与可解析性&#xff0c;成为实际部署中的关键问题。…

32 C 语言字符处理函数详解:isalnum、isalpha、iscntrl、isprint、isgraph、ispunct、isspace

1 isalnum() 函数 1.1 函数原型 #include <ctype.h>int isalnum(int c); 1.2 功能说明 isalnum() 函数用于检查传入的整数参数是否为 ASCII 编码的字母或数字字符&#xff08;A - Z、a - z、0 - 9&#xff0c;对应 ASCII 值 65 - 90、97 - 122、48 - 57&#xff09;。…

在网络排错中,经常会用到的操作命令和其作用

在网络排错中&#xff0c;经常会用到的操作命令和其作用 网络排错是确保网络连接正常运行的重要环节&#xff0c;通过使用一系列工具和命令&#xff0c;可以有效诊断和解决网络问题。以下是常用的网络排错命令及其作用&#xff1a; 1.ping ping 是一个用于测试主机之间连通性…

C++中友元(friend)高级应用和使用示例

下面列出几个 高级友元应用场景 与典型设计模式&#xff0c;并配以示例&#xff0c;帮助大家在实际项目中灵活运用 friend 机制。 1. ADL 友元注入&#xff08;“注入式友元”&#xff09; 场景&#xff1a;为某个类型定义非成员操作符&#xff08;如算术、流插入等&#xff0…

TCP相关问题 第一篇

TCP相关问题1 1.TCP主动断开连接方为什么需要等待2MSL 如上图所示:在被动链接方调用close&#xff0c;发送FIN时进入LAST_ACK状态&#xff0c;但未收到主动连接方的ack确认&#xff0c;需要被动连接方重新发送一个FIN&#xff0c;而为什么是2MSL&#xff0c;一般认为丢失ack在…

STM32启动文件学习(startup_stm32f40xx.s)

原代码 ;******************** (C) COPYRIGHT 2016 STMicroelectronics ******************** ;* File Name : startup_stm32f40xx.s ;* Author : MCD Application Team ;* version : V1.8.0 ;* date : 09-November-2016 ;* Desc…

uni-app学习笔记二十三--交互反馈showToast用法

showToast部分文档位于uniapp官网-->API-->界面&#xff1a;uni.showToast(OBJECT) | uni-app官网 uni.showToast(OBJECT) 用于显示消息提示框 OBJECT参数说明 参数类型必填说明平台差异说明titleString是提示的内容&#xff0c;长度与 icon 取值有关。iconString否图…

【Ragflow】26.RagflowPlus(v0.4.0):完善解析逻辑/文档撰写模式全新升级

概述 在历经半个月的间歇性开发后&#xff0c;RagflowPlus再次迎来一轮升级&#xff0c;正式发布v0.4.0。 开源地址&#xff1a;https://github.com/zstar1003/ragflow-plus 更新方法 下载仓库最新代码&#xff1a; git clone https://github.com/zstar1003/ragflow-plus.…

【论文解读】Toolformer: 语言模型自学使用工具

1st author: ‪Timo Schick‬ - ‪Google Scholar‬ paper: Toolformer: Language Models Can Teach Themselves to Use Tools | OpenReview NeurIPS 2023 oral code: lucidrains/toolformer-pytorch: Implementation of Toolformer, Language Models That Can Use Tools, by…

Spring 官方推荐构造函数注入

1. 依赖关系明确 构造函数注入可以清晰地声明类的依赖关系&#xff0c;所有必需的依赖项都通过构造函数参数传递&#xff0c;使得代码的可读性更高。这种方式让类的使用者能够直观地了解类的依赖&#xff0c;而不需要通过注解或反射来猜测。 2. 增强代码健壮性 构造函数注入…

[深度学习]搭建开发平台及Tensor基础

一、实验目的 1. 掌握Windows下PyTorch 深度学习环境的配置 2. 掌握一种PyTorch开发工具 3. 理解张量并掌握Tensor的常用操作&#xff08;创建、调整形状、加、减、乘、除、取绝对值、比较操作、数理统计操作 4. 掌握Tensor与Numpy的互相转换操作 5. 掌握Tensor 的降维和…

【Zephyr 系列 14】使用 MCUboot 实现 BLE OTA 升级机制:构建安全可靠的固件分发系统

🧠关键词:Zephyr、MCUboot、OTA 升级、BLE DFU、双分区、Bootloader、安全固件管理 📌面向读者:希望基于 Zephyr 为 BLE 设备加入安全 OTA 升级功能的开发者 📊预计字数:5200+ 字 🧭 前言:为什么你需要 OTA? 随着设备部署数量增多与产品生命周期延长,远程升级(…

App Search 和 Workplace Search 独立产品现已弃用

作者&#xff1a;来自 Elastic The Search Product Team App Search 和 Workplace Search 的核心功能已集成到 Elasticsearch 和 Kibana 中。 我们宣布在 9.0 版本中弃用 App Search 和 Workplace Search。 如果你是 Elastic 的客户&#xff0c;当前正在使用 App Search 和 Wo…

Spring Boot + OpenAI 构建基于RAG的智能问答系统

一、技术架构设计 1.1 系统架构图 [前端]│▼ (HTTP/REST) [Spring Boot Controller]│▼ (Service Call) [问答处理服务层]├─▶ [知识库检索模块] ──▶ [向量数据库]└─▶ [OpenAI集成模块] ──▶ [OpenAI API]│▼ [结果组装与返回] 1.2 技术选型 组件技术栈版本要求…

Oracle实用参考(13)——Oracle for Linux物理DG环境搭建(2)

13.2. Oracle for Linux物理DG环境搭建 Oracle 数据库的DataGuard技术方案,业界也称为DG,其在数据库高可用、容灾及负载分离等方面,都有着非常广泛的应用,对此,前面相关章节已做过较为详尽的讲解,此处不再赘述。 需要说明的是, DG方案又分为物理DG和逻辑DG,两者的搭建…

【论文阅读29】区间预测CIPM(2025)

这篇论文主要研究的是滑坡位移的区间预测方法&#xff0c;提出了一种新型的预测模型&#xff0c;叫做复合区间预测模型&#xff08;CIPM&#xff09;&#xff0c;并以三峡库区的白家堡滑坡为案例进行了应用和验证。论文的核心内容和贡献包括&#xff1a; 背景与问题 滑坡位移预…

Linux 文件系统底层原理笔记:磁盘结构、ext2 文件系统与软硬链接解析

文章目录 一、理解硬件1.1 磁盘、服务器、机柜、机房1.2 磁盘物理结构1.3 磁盘的存储结构1.4 磁盘的逻辑结构1.4.1 理解过程1.4.2 真实过程 1.5 CHS && LBA地址 二、引入文件系统2.1 引入"块"概念2.2 引入"分区"概念2.3 引入"inode"概念…

75Qt窗口_Qt窗口概览

Qt 窗⼝ 是通过 QMainWindow类 来实现的。 QMainWindow 是⼀个为⽤⼾提供主窗⼝程序的类&#xff0c;继承⾃ QWidget 类&#xff0c;并且提供了⼀个预定义的布局。 QMainWindow 包含 ⼀个菜单栏&#xff08;menu bar&#xff09;、多个⼯具栏(tool bars)、多个浮动窗⼝&#x…