【Linux】线程——线程的概念、线程的特点、线程的优点和缺点、线程和进程、线程函数的使用

文章目录

  • Linux线程
    • 1. 线程的概念
      • 1.1 什么是线程
    • 2. 线程的特点
      • 2.1 线程的优点
      • 2.2 线程的缺点
      • 2.4 线程和进程
    • 3. 线程函数的使用
      • pthread_create() 创建线程
      • pthread_self() 获取线程ID
      • pthread_exit() 线程终止
      • pthread_cancel() 线程取消
      • pthread_join() 线程等待
      • pthread_detach()线程分离

Linux线程

1. 线程的概念

  我们之前认识到,进程是程序的一个执行实例,是正在执行的程序等,这是从进程本身来看的,从内核上看,进程担当分配系统资源(CPU时间,内存)的实体,是联系硬件和软件的桥梁。

  通常进程包含:进程独立的程序地址空间和页表,通常是我们说的虚拟内存;
进程拥有系统分配的各种资源;标识进程唯一性和保存执行信息的进程控制块(PCB);执行的程序代码和相关的数据;打开的文件和设备;进程上下文。

  当一个进程要执行大量任务的时候,一个单进程通常只有一个执行流,只能执行一个时间片,这样无法提高计算机的运行效率,所以我们要在进程中使用线程。

  通过线程,我们可以提高计算机的运行效率。线程是进程中的执行单元,多个线程可以共享进程的资源,如内存空间、文件描述符等。这样使得多个线程可以在同一个进程中并发执行,将复杂的任务分解为多个子任务,每一个子任务有线程执行,充分利用CPU的时间片,减少等待时间,提高系统运行效率。

  简单总结:

  进程因为包含很多资源,开销较大,线程在进程内部开销较小。

  多线程有多个时间片且并发执行,资源共享,运行效率较高。

  

1.1 什么是线程

  (1)在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。

  (2)一切进程至少都有一个执行线程。

  (3)线程在进程内部运行,本质是在进程地址空间内运行。

  (4)在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化,所以Linux 下的线程看作轻量级进程。

  (5)透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

  总结:线程是进程中的一个实体,作为系统调度和分派的基本单位。

在这里插入图片描述

  

2. 线程的特点

2.1 线程的优点

  (1)创建一个新线程的代价要比创建一个新进程小得多

  (2)与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

  (3)线程占用的资源要比进程少很多

  (4)能充分利用多处理器的可并行数量

  (5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

  (6)计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

  (7)I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

  

2.2 线程的缺点

  (1)性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  (2)健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  (3)缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  (4)编程难度提高:编写与调试一个多线程程序比单线程程序困难得多

  

2.4 线程和进程

进程和线程的区别:

  (1)调度单位:进程是资源分配的基本单位,线程是调度的基本单位。

  (2)资源拥有:进程拥有独立的地址空间和系统资源,线程共享进程资源,但也拥有自己的一部分资源:线程ID、一组寄存器、栈、errno、信号屏蔽字、调度优先级。

  (3)系统开销:创建或撤销进程时,系统需要分配或回收资源,开销较大;线程的创建和撤销开销相对较小。

  (4)通信方式:进程间通信较为复杂,需要使用特定的进程间通信机制,如管道、消息队列等;线程间通信相对简单,可以通过共享内存等方式直接进行。

  (5)健壮性:进程间相互独立,一个进程的崩溃一般不会影响其他进程;线程一个线程的崩溃可能影响整个进程。

  此外因为进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:文件描述符表,每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录,用户id和组id。

在这里插入图片描述

  

3. 线程函数的使用

  POSIX线程库:与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的,要使用这些函数库,要通过引入头文<pthread.h>,链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

pthread_create() 创建线程

功能:创建一个新的线程

原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
 void *(*start_routine)(void*), void *arg);

参数:

  thread:返回线程ID
  attr:设置线程的属性,attr为NULL表示使用默认属性
  start_routine:是个函数地址,线程启动后要执行的函数
  arg:传给线程启动函数的参数

返回值:

  成功返回0;失败返回错误码

错误检查:

  传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。

  pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。

  对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小。

  

进程地址空间布局

  pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。

  前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

  pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。

  线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID。

  pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

在这里插入图片描述

  

pthread_self() 获取线程ID

pthread_t pthread_self(void);

功能:通过 pthread_self() 函数可以获取到当前线程的 ID。

  

pthread_exit() 线程终止

功能:线程终止

原型:

void pthread_exit(void *value_ptr);

参数:
  value_ptr:value_ptr不要指向一个局部变量。

返回值:
  无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。

  如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  (1)从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

  (2)线程可以调用pthread_ exit终止自己。

  (3)一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

  需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

  

pthread_cancel() 线程取消

功能:取消一个执行中的线程。

原型:

int pthread_cancel(pthread_t thread);

参数:
  thread:线程ID

返回值:
  成功返回0;失败返回错误码。

  

pthread_join() 线程等待

功能:等待线程结束

原型:

int pthread_join(pthread_t thread, void **value_ptr);

参数:
  thread:线程ID
  value_ptr:它指向一个指针,后者指向线程的返回值

返回值:
  成功返回0;失败返回错误码

  

为什么需要线程等待?

  1. 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。

  2. 创建新的线程不会复用刚才退出线程的地址空间。

  

  调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  (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参数。

  

pthread_detach()线程分离

  功能:将一个线程设置为分离状态。当一个线程被分离后,它所占用的资源在其终止时会由系统自动回收,而无需其他线程通过 pthread_join 来等待并回收资源

int pthread_detach(pthread_t thread);

  默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

  如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

  可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());

  

使用多线程模拟一个简单的多线程加减乘除计算过程

#include <iostream>
#include <pthread.h>
#include <vector>
#include <cstring>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <ctime>

char op[4]={'+','-','*','/'};

//计算请求
class Request
{
public:
    Request(int id,int n1,int n2,int op)
        :_id(id),_n1(n1),_n2(n2),_op(op)
    {}
public:
    int _id;
    int _n1;
    int _n2;
    char _op;
};

//计算结果
class Response
{
public:
    Response(std::string request, int result)
        :_request(request),_result(result),_errno(0)
    {}
public:
    std::string _request;
    int _result;
    int _errno;
};

//执行计算的函数
void* handler(void *args)
{
    Request *rq=static_cast<Request*>(args);
    std::string req="thread "+std::to_string(rq->_id)+" : "+std::to_string(rq->_n1)\
                    +rq->_op+std::to_string(rq->_n2);
    int res,flag=0;
    switch (rq->_op) 
    {
    case '+':
        res = rq->_n1 + rq->_n2;
        break;
    case '-':
        res = rq->_n1 - rq->_n2;
        break;
    case '*':
        res = rq->_n1 * rq->_n2;
        break;
    case '/':
        if (rq->_n2 != 0) 
        {
            res = rq->_n1 / rq->_n2;
        } 
        else 
        {
            flag=1;
            std::cerr << "Error: Division by zero!" << std::endl; 
        }
        break;
    }

    Response *rsp=new Response(req,res);
    if(flag==1) rsp->_errno=1;
    std::cout<<"thread is working"<<std::endl;
    //std::cout<<rq->_id<<" thread result : "<<rq->_n1<<"+"<<rq->_n2<<"="<<rsp->_result<<std::endl;
    //usleep(1000);
    return rsp;
}

//我们使用多线程模拟一个简单的多线程加减乘除计算过程
void test2()
{
    //设置随机数种子
    std::srand(static_cast<unsigned int>(std::time(nullptr)));

    //模拟10次加减乘除计算过程
    std::vector<pthread_t> threads;
    std::vector<Response*> responses; // 用于存储每个线程的返回值

    for(int i=1;i<=10;i++)
    {
        pthread_t tid;
        int n1=rand()%10;
        int n2=rand()%10;
        Request *rq=new Request(i,n1,n2,op[n1%4]);
        pthread_create(&tid,nullptr,handler,rq);
        threads.push_back(tid); // 将线程句柄存储起来
    }

    for(auto& thread:threads)
    {
        void *ret;
        pthread_join(thread,&ret);
        Response *rsp = static_cast<Response *>(ret);
        std::cout<<rsp->_result<<std::endl;
        std::cout << "rsp->result: " << rsp->_result <<std::endl;    
        responses.push_back(rsp); // 将 Response 对象存储起来
    }

    // 输出每个线程的计算结果
    for (auto rsp : responses)
    {
        if(rsp->_errno==1)  
        {
            std::cout <<rsp->_request << " result: " << "division by zero error" << std::endl;
        }
        else 
        {
            std::cout <<rsp->_request << " result: " << rsp->_result << std::endl;
        }
        delete rsp; // 释放每个 Response 对象的内存
    }
}

在这里插入图片描述

  

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Element-UI - el-table中自定义图片悬浮弹框 - 位置优化

该篇为前一篇“Element-UI - 解决el-table中图片悬浮被遮挡问题”的优化升级部分&#xff0c;解决当图片位于页面底部时&#xff0c;显示不全问题优化。 Vue.directive钩子函数已在上一篇中详细介绍&#xff0c;不清楚的朋友可以翻看上一篇&#xff0c; “Element-UI - 解决el-…

3.js - 色调映射(renderer.toneMapping)

// ts-nocheck// 引入three.js import * as THREE from three// 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 导入lil.gui import { GUI } from three/examples/jsm/libs/lil-gui.module.min.js// 导入tween import * as TWEEN…

不同操作系统下的换行符

1. 关键字2. 换行符的比较3. ASCII码4. 修改换行符 4.1. VSCode 5. 参考文档 1. 关键字 CR LF CRLF 换行符 2. 换行符的比较 英文全称英文缩写中文含义转义字符ASCII码值操作系统Carriage ReturnCR回车\r13MacIntosh&#xff08;早期的Mac&#xff09;LinefeedLF换行/新行\…

Windows下载安装配置并使用Redis(保姆级教程)

文章目录 1、Redis的下载与安装 2、Redis的使用 3、Redis的图形界面客户端 4、Redis开机自启动 1、Redis的下载与安装 下载Redis&#xff1a;https://pan.baidu.com/s/1zBonkO2y6AZeqCdRe0W5ow?pwd9999 提取码: 9999 下载后直接解压就可以使用了 2、Redis的使用 我们…

全网最佳硕士研究生复试简历模板

硕士研究生复试简历模板 ✨ 简介 提供了一个适用于国内硕士研究生复试的个人简历模板。该模板通过统一的“样式”形成规范的Word格式&#xff0c;是目前研究生复试的最佳简历模板之一。模板使用“华文中宋”字体&#xff0c;如您的电脑中未安装此字体&#xff0c;请提前安装。…

软件测试与质量保证 | 云班课选择题库

目录 第1章课后习题 第2章课后习题 第3章课后习题 第4章课后习题 第5章课后习题 第6章课后习题 第7章课后习题 第8章课后习题 第9章课后习题 第10章课后习题 第11章课后习题 第12章课后习题 第13章 测试相关未分类习题 第1章课后习题 1. 与质量相关的概念包括 &a…

去中心化革命:探索区块链技术的前沿

随着信息技术的飞速发展&#xff0c;区块链技术作为一种新兴的去中心化解决方案&#xff0c;正逐渐改变着我们的经济、社会和技术格局。本文将从区块链的基本原理、当前的应用实例以及未来的发展趋势三个方面&#xff0c;深入探讨区块链技术在革命性变革中的角色和影响。 1. 区…

springboot 自定义的全局捕获异常失效

背景&#xff1a;springbootspringcloud 分布式微服务。 问题&#xff1a;公共模块在使用RestControllerAdvice全局捕获异常时&#xff0c;捕获不到子服务抛出的相应异常 首先看一下全局异常组件有么有被扫描到 如何查看&#xff0c;很简单只需要写一段类加载打印代码&#x…

【hot100】跟着小王一起刷leetcode -- 739. 每日温度

【hot100】跟着小王一起刷leetcode -- 739. 每日温度 739. 每日温度题目解读思路 代码总结 739. 每日温度 题目解读 739. 每日温度 老规矩&#xff0c;咱先看下题目。总结下来就是&#xff0c;你要返回一个answer数组&#xff0c;answer[i]中存储的应该是temperatures数组中…

Android跨进程通信,binder传输数据过大导致客户端APP,Crash,异常捕获,监听异常的数值临界值,提前Hook拦截。

文章目录 Android跨进程通信&#xff0c;binder传输数据过大导致Crash&#xff0c;异常捕获&#xff0c;监听异常的数值临界值&#xff0c;提前Hook拦截。1.binder在做跨进程传输时&#xff0c;最大可以携带多少数据1.1有时候这个1m的崩溃系统捕获不到异常&#xff0c; 2.监测异…

深度学习与飞桨 PaddlePaddle Fluid

编辑推荐 飞桨PaddlePaddle是百度推出的深度学习框架&#xff0c;不仅支撑了百度公司的很多业务和应用&#xff0c;而且随着其开源过程的推进&#xff0c;在其他行业得到普及和应用。 本书基于2019年7月4日发布的飞桨PaddlePaddle Fluid 1.5版本&#xff08;后续版本会兼容旧版…

[工业网络] 模型建立

普渡大学ICS参考模型 普渡企业参考架构&#xff08;PERA&#xff09;是由西奥多J威廉姆斯&#xff08;Theodore J. Williams&#xff09;和普渡大学计算机集成制造工业大学联盟的成员在1990年代开发的企业架构参考模型。该模型被ISA-99&#xff08;现为ISA/IEC 62443&#xff…

warning: LF will be replaced by CRLF the next time Git touches it warning

问题&#xff1a; warning: in the working copy of , LF will be replaced by CRLF the next time Git touches it warning: 今天上传git时报错&#xff0c;使用Ai&#xff1b;得知&#xff1b; 解决&#xff1a; 将 Git 配置为不自动转换换行符&#xff0c;使用以下命令…

snap和apt的区别简单了解

Linux中没有tree命令的时候提示安装的时候出现了两个命令&#xff0c;简单看了看两者有何区别&#xff08;一般用apt就可以了&#xff09;&#xff1a; sudo snap install tree 和 sudo apt install tree 这两个命令都是用来安装 tree 命令行工具的&#xff0c;但它们使用的是不…

webSocket网页通信---使用js模拟多页面实时通信

webSocket是什么 WebSocket是一种先进的网络技术&#xff0c;它提供了一种在单个TCP连接上进行全双工通信的能力。传统的基于HTTP的通信是单向的&#xff0c;即客户端发起请求&#xff0c;服务器响应请求&#xff0c;然后连接关闭。但是&#xff0c;WebSocket允许服务器和客户端…

Spring Boot2.x教程:(四)Spring Boot2.6及之后版本整合Knife4j的问题

Spring Boot2.6及之后版本整合Knife4j的问题 1、概述2、问题出现原因及解决办法3、拓展3.1、为什么发生这种变化 4、总结 大家好&#xff0c;我是欧阳方超&#xff0c;可以扫描下方二维码关注我的公众号“欧阳方超”&#xff0c;后续内容将在公众号首发。 1、概述 今天在2.7…

Raylib 坐标系适应与GPU绘制参数

通过750 - 鼠标坐标&#xff0c;把原点在左上角的鼠标坐标变成左下角 实现输入数据后的坐标系同GPU原点在左下角坐标相同&#xff0c; 比数组0&#xff0c;0对应左上角好&#xff0c; 此时实际上数组0&#xff0c;0对应左下角 #include <raylib.h> // 感受&#xff1a…

如何用Python实现三维可视化?

Python拥有很多优秀的三维图像可视化工具&#xff0c;主要基于图形处理库WebGL、OpenGL或者VTK。 这些工具主要用于大规模空间标量数据、向量场数据、张量场数据等等的可视化&#xff0c;实际运用场景主要在海洋大气建模、飞机模型设计、桥梁设计、电磁场分析等等。 本文简单…

OpenELM:开启开放训练和推理框架的高效语言模型家族

随着大模型模型规模的增长&#xff0c;这些强大工具的透明度、可复现性和对数据偏见的敏感性也引起了人们的关注。这些问题不仅关系到研究的开放性和公平性&#xff0c;也关系到模型输出的可信度和安全性。为了应对这些挑战&#xff0c;Apple的研究团队发布了名为OpenELM的新一…

守护进程到底是什么?如何创建?(图文并茂,你不得不看的一篇文章)

目录 守护进程&#xff08;Daemon Process&#xff09;详解 守护进程的特点 创建守护进程的步骤 用守护进程实现输入Hello功能 守护进程的用途 如何查看我们的守护进程&#xff1f; 1. ps 命令 2. top 命令 总结 守护进程&#xff08;Daemon Process&#xff09;详解 …