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

Linux下的多线程

前面学习了进程、文件等概念,接下里为大家引入线程的概念

多线程

  • 线程是什么?
  • 为什么要有线程?
  • 线程的优缺点
  • Linux线程操作
    • 线程创建
    • 线程等待
    • 线程终止
    • 线程分离
  • 线程间的私有和共享数据
  • 理解线程库和线程id
  • 深刻理解Linux多线程(重点)

线程是什么?

  • 线程是一个执行分支,执行粒度比进程更细,调度成本更低。
  • 线程是进程内部的一个执行流
  • 线程是CPU调度的基本单位,进程是承担分配系统资源的基本实体

为什么要有线程?

多线程编程在现代计算中具有显著的优势,主要体现在以下几个方面:

  1. 发挥多核CPU优势:
    现代处理器普遍拥有多个核心,多线程能够确保这些核心得到充分利用。通过创建多个线程,程序可以同时在不同核心上执行不同的任务部分,从而提升并行处理能力,整体提高程序的运行效率。
  2. 防止阻塞和提高响应性:
    当一个线程在等待IO操作(如磁盘读写、网络通信)或执行耗时计算时,操作系统可以调度其他线程继续执行,避免了整个进程停滞,提高了系统的响应速度。
  3. 并发处理能力增强:
    多线程使得系统能够并发地处理多个用户请求或执行多个独立的任务。这对于高并发场景下的服务器应用、实时数据处理、大规模并行计算等尤为关键,可以显著提升系统吞吐量和服务质量。
  4. 模块化与简化设计:
    在复杂的软件架构中,多线程有助于将大的任务分解成若干个可管理的子任务,并分配给不同的线程来处理。这不仅使得代码逻辑更加清晰,也更容易进行模块化开发和维护。
  5. 改善用户体验:
    用户界面应用程序中,主线程负责处理UI事件,后台线程则可以处理长时间运行的操作(例如加载数据、文件压缩解压)。这样可以在不影响用户界面交互的同时完成大量工作,提升了用户体验。
  6. 资源利用率:
    相对于为每个并发任务创建新的进程,线程间的切换开销较小,因为它们共享同一进程的地址空间和其他资源。这降低了系统资源消耗,尤其是在内存有限的情况下。

然而,多线程编程也带来了一系列挑战,包括但不限于:资源共享引发的数据竞争问题、死锁、优先级反转等并发控制难题。因此,在享受多线程带来的性能提升时,开发者需要精心设计和实现线程间同步机制,以保证程序的正确性和稳定性。

线程的优缺点

优点:

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

缺点:

  • 性能损失
    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。
  • 健壮性降低
    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。(一个线程崩溃了,系统发送信号是以进程为单位的,所以整个进程都会崩溃。)
  • 缺乏访问控制
    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响(多线程共享地址空间)。

Linux线程操作

使用Linux线程接口的时候,我们需要导入线程库。例如:

g++ -o main main.cpp -std=c++11 -lpthread

这个pthread就是Linux自带的线程库,但是我们上面线程概念提到了Linux没有真正的线程,而是用进程模拟的线程(LWP),所以Linux也没有真正的线程接口,Linux提供的是轻量级进程的系统接口,然后对这个接口进行封装,封装成线程库,在用户的角度看这就是线程控制的接口,从而完成对线程的控制。(任何操作系统都会自带线程库)

线程创建

线程创建使用pthread_create这个函数接口,需要包含头文件pthread.h
在这里插入图片描述
参数解析:

  • pthread_t *thread //线程id
  • const pthread_attr_t *attr //设置线程属性,一般为nullptr
  • void *(start_routine) (void ) //回调函数,会执行传入的函数指针(返回值void,参数void
  • void *arg //可以作为函数参数

线程等待

在主线程调用这个函数会让主线程阻塞等待线程结束,并不是所有的情况都必须调用这个函数。
在这里插入图片描述
传入对应的线程id,并且阻塞等待。第二个参数是一个输出型参数,用于接收线程返回值。

线程终止

线程终止可能由以下几种情况造成:

  1. 一个线程正常情况下结束有可能是线程函数执行结束了,return void*。
  2. 还有一种就是调用这个函数,线程调用这个函数让自己退出。
    在这里插入图片描述
  3. 主线程调用这个函数就可以取消正在执行的线程。返回值为-1。
    在这里插入图片描述

线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

分离线程的接口,只需要传入线程id就可以了。
在这里插入图片描述
线程分离之后是不可以join的,join会返回报错码,线程分离是一种属性状态,如果主线程join等待某个线程,会查看这个线程的状态是否是分离的。如果是分离状态就会直接报错。如果先join线程,再分离线程,会检测不到。

例如这样就会报错

void *run_thread(void *args){//pthread_detach(pthread_t pthread_self()); //可以自己分离自己int cnt = 5;while(cnt){cout<<"我是线程:"<<cnt--<<endl;sleep(1);}
}
int main(){pthread_t t1;pthread_create(&t1,nullptr,run_thread,nullptr);pthread_detach(t1);//可以再主线程里面分离int n = pthread_join(t1,nullptr);if(n!=0)//如果n!=0表示阻塞等待失败,打印返回的错误码{cout<<"error:"<<n<<":"<<strerror(n)<<endl;}return 0;}

在这里插入图片描述

线程间的私有和共享数据

线程共享进程数据,但是也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器
  • errno
  • 信号屏蔽字
  • 调度优先级

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

理解线程库和线程id

我们学习的Linux里面其实是没有真正意义上的线程的,Linux用进程模拟的线程。所谓的线程就是一个轻量级的进程,所以Linux提供了对轻量级进程操作的接口,而我们用的线程库就是对这些接口进行了封装。从用户的角度是对线程进行操作,但是从OS的角度是对轻量级进程的操作。

这个线程库是一个用户级的动态库,所以进程想要用这个库,必须先加载到内存,然后映射到进程地址空间的共享区中。

这个线程库里面可能会创建很多个线程,所以需要对线程进行管理,所以先描述,再组织。每个线程都有一个结构体来对这些线程进行统一管理。
在这里插入图片描述

线程库中有很多个线程,每个线程都是向数组一样排列,每个线程都有一个起始地址,这个起始地址就是线程id,把这个线程id传给其他线程,就可以获取该线程的属性信息。

每个线程都拥有自己的栈结构,主线程用的是地址空间的栈。

深刻理解Linux多线程(重点)

当一个进程创建子进程时,需要创建PCB、进程地址空间、页表等。是非常独立的。一个进程内可以有多个线程,那么线程是怎么创建的呢?

  • 给一个进程创建线程时,只会创建一个PCB,这个PCB还是会指向这个进程,进程地址空间内部有代码区,每个线程指向代码区中不同的代码区域,一个线程对应一个函数代码,这样一个线程就是一个执行流。这就是为什么线程是进程内部的一个执行流。
  • 线程执行粒度更细因为可以执行进程中不同的代码,控制粒度更细。
  • 调度成本更低因为PCB中存放进程地址空间的地址,当CPU切换PCB时,发现不用切换加载进程地址空间以及页表等一系列操作。但是最重要还是不用切换cache(cache是一个集成在CPU里面的硬件,也叫做高速缓存器,当我们访问内存中的代码的时候,会预先加载一部分代码到cache中,减少IO,提升效率,这个也叫做局部性原理。CPU切换的线程如果是同一个进程,cache不用切换数据,使得切换成本更低)。
  • 之前谈到进程是CPU的基本调度单位,因为之前谈论的进程都是一个执行流,一个进程只有一个PCB,而多线程这里,一个进程有多个PCB,也就是多个执行流,对于CPU来说其实调度切换的是进程还是线程,CPU并不知道,也并不重要,CPU只需要可以通过PCB访问进程地址空间,通过页表映射到内存就可以了。
    但是并不是所有的操作系统都是这样设计多线程的,这种是Linux下的多线程,而windows的里面线程和进程是不同的,Windows下的线程叫做TCB(线程控制块),而线程是进程的一个执行流,必须遵守执行粒度更细,调度成本更低,所以Windows的设计是比较复杂的。Windows里面是有真线程的,Linux下则是用进程的方案去模拟线程,所以Linux没有真正意义上的线程,都叫做轻量级进程(LWP)。Linux对比Windows复用代码结构,更简单,好维护,效率更高。

一个进程里面有多个执行流,有一个执行流是主执行流。每个进程都有一个pid,一个进程里面有多个执行流,每个执行流的pid当然都是一样的,但是每个执行流都有一个LWP是不一样的,CPU根据LWP来进行基本的调度。切换执行流,如果pid不变证明还是一个进程,不需要切换地址空间、cache等操作,如果pid变了,证明不是同一个进程了。

代码证明多线程有多个LWP,有同一个pid

void *run_thread1(void *args){while(true){cout<<"我是执行流1:"<<*((int*)args)<<endl;sleep(1);}
}
void *run_thread2(void *args){while(true){cout<<"我是执行流2:"<<*((int*)args)<<endl;sleep(1);}
}
void *run_thread3(void *args){while(true){cout<<"我是执行流3:"<<*((int*)args)<<endl;sleep(1);}
}
int main(){pthread_t t1,t2,t3;int th1=1;pthread_create(&t1,nullptr,run_thread1,&th1);int th2=2;pthread_create(&t2,nullptr,run_thread2,&th2);int th3=3;pthread_create(&t3,nullptr,run_thread3,&th3);while(true){cout<<"我是主执行流"<<endl;sleep(1);}return 0;
}

执行结果
在这里插入图片描述
这段代码使用了一下多线程,证明一个线程有一个LWP。主执行流的LWP和进程的pid是相同的,所以之前学习进程说的CPU根据pid调度进程也是对的。

这就是Linux下的多线程,后续会更新互斥和同步的文章,多多支持。

相关文章:

Linux下的多线程

前面学习了进程、文件等概念&#xff0c;接下里为大家引入线程的概念 多线程 线程是什么&#xff1f;为什么要有线程&#xff1f;线程的优缺点Linux线程操作线程创建线程等待线程终止线程分离 线程间的私有和共享数据理解线程库和线程id深刻理解Linux多线程&#xff08;重点&a…...

Nginx+React在Docker中实现项目部署

一、引言 Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;也能够处理 IMAP/POP3/SMTP 服务&#xff0c;由 Igor Sysoev 开发并在 2004 年首次公开发布。它以处理静态内容、提供反向代理服务以及其高稳定性、低资源消耗而广受欢迎。Nginx 能够通过非阻塞方式处理多个连…...

Centos 7.5 安装 NVM 详细步骤

NVM&#xff08;Node Version Manager&#xff09;是一个用于管理Node.js版本的工具&#xff0c;它可以让你轻松地在多个版本之间切换。NVM 通过下载和管理 Node.js 的多个版本&#xff0c;为用户提供了一种灵活的方式来使用不同版本的 Node.js。如果你需要更多关于NVM的信息&a…...

【python】绘制春节烟花

一、Pygame库春节烟花示例 下面是一个使用Pygame实现的简单春节烟花效果的示例代码。请注意&#xff0c;运行下面的代码之前&#xff0c;请确保计算机上已经安装了Pygame库。 import pygame import random import math from pygame.locals import *# 初始化pygame pygame.ini…...

ChatPromptTemplate和AI Message的用法

ChatPromptTemplate的用法 用法1&#xff1a; from langchain.chains import LLMChain from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_community.tools.tavily_search import TavilySear…...

Terraform实战(三)-在AWS上尝试Terraform的Vault Provider

使用自Terraform 0.8起添加的Vault Provider后&#xff0c;aws云基础设施尝试从Vault而不是tfvars或环境变量中读取AWS凭证。 1 什么是vault&#xff1f; vault是一种由Hashicorp发布的用于管理机密信息的工具。 2 aws使用Terraform的Vault Provider 2.1 创建静态密钥 以开…...

【Nicn的刷题日常】之有序序列合并

1.题目描述 描述 输入两个升序排列的序列&#xff0c;将两个序列合并为一个有序序列并输出。 数据范围&#xff1a; 1≤&#xfffd;,&#xfffd;≤1000 1≤n,m≤1000 &#xff0c; 序列中的值满足 0≤&#xfffd;&#xfffd;&#xfffd;≤30000 0≤val≤30000 输入描述…...

PostgreSql与Postgis安装

POstgresql安装 1.登录官网 PostgreSQL: Linux downloads (Red Hat family) 2.选择版本 3.安装 ### 源 yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm ### 客户端 yum install postgresql14 ###…...

【Spring连载】使用Spring Data访问Redis(九)----Redis流 Streams

【Spring连载】使用Spring Data访问Redis&#xff08;九&#xff09;----Redis流 Streams 一、追加Appending二、消费Consuming2.1 同步接收Synchronous reception2.2 通过消息监听器容器进行异步接收Asynchronous reception through Message Listener Containers2.2.1 命令式I…...

MySQL:从基础到实践(简单操作实例)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 下载前言一、MySQL是什么&#xff1f;二、使用步骤1.引入库2.读入数据 提交事务查询数据获取查询结果总结 下载 点击下载提取码888999 前言 在现代信息技术的世界…...

Flink流式数据倾斜

1. 流式数据倾斜 流式处理的数据倾斜和 Spark 的离线或者微批处理都是某一个 SubTask 数据过多这种数据不均匀导致的&#xff0c;但是因为流式处理的特性其中又有些许不同 2. 如何解决 2.1 窗口有界流倾斜 窗口操作类似Spark的微批处理&#xff0c;直接两阶段聚合的方式来解决…...

零基础学编程系列,从入门到精通,中文编程开发语言工具下载,编程构件容器件之控制面板构件用法

零基础学编程系列&#xff0c;从入门到精通&#xff0c;中文编程开发语言工具下载&#xff0c;编程构件容器件之控制面板构件用法 一、前言 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 编程工具及实例源码文件下载可以点击最下方官网卡片——软件下载…...

使用PowerBI 基于Adventure Works案例分析

Adventure Works案例分析 前言 数据时代来临&#xff0c;但一个人要顺应时代的发展是真理。 数据分析的核心要素 那数分到底是什么&#xff1f; 显然DT 并不等同于 IT&#xff0c;我们需要的不仅仅是更快的服务器、更多的数据、更好用的工具。这些都是重要的组成部分&…...

人工智能之估计量评估标准及区间估计

评估估计量的标准 无偏性:若估计量( X 1 , X 2 , ⋯   , X n X_1,X_2,\cdots,X_n X1​,X2​,⋯,Xn​)的数学期望等于未知参数θ,即 E ( θ ^ ) = θ E(\hat\theta)=\theta E(θ^)=θ 则称 θ ^ \hat\theta θ^为θ的无偏估计量。 估计量 θ ^ \hat\theta θ^的值不一定就是…...

Ubuntu权限相关命令

文章目录 文件夹/文件带锁(图标) 解锁无密码访问文件/目录sudo usermod -aG sudo your_username其他后记 命令参考: https://www.cnblogs.com/alongdidi/p/linux_ownership_permission.html 文件夹/文件带锁(图标) 解锁 递归解锁当前路径下的所有文件夹以及文件(包括子文件)su…...

RTE2023第九届实时互联网大会:揭秘未来互联网趋势,PPT分享引领行业新思考

随着互联网的不断发展&#xff0c;实时互动技术正逐渐成为新时代的核心驱动力。 在这样的背景下&#xff0c;RTE2023第九届实时互联网大会如期而至&#xff0c;为业界人士提供了一个探讨实时互联网技术、交流创新理念的绝佳平台。 本文将从大会内容、PPT分享价值等方面&#…...

Hadoop-生产调优

第1章 HDFS-核心参数 1.1 NameNode内存生产配置 1&#xff09;NameNode 内存计算 每个文件块大概占用 150 byte&#xff0c;一台服务器 128G 内存为例&#xff0c;能存储多少文件块呢&#xff1f; 128 * 1024 * 1024 * 1024 / 150byte ≈ 9.1 亿G MB KB Byte 2&#xff09…...

Elasticsearch基于分区的索引策略

分区索引&#xff0c;或者更常见的说法&#xff0c;基于分区的索引策略&#xff0c;是一种按照特定规则&#xff08;如时间、地理位置、业务线等&#xff09;将数据分散到多个不同的索引中的方法。这种做法可以提高Elasticsearch的性能和可管理性&#xff0c;尤其是在处理大量数…...

ASP.NET Core MVC 控制查询数据表后在视图显示

如果是手动写代码&#xff0c;不用VS自带的一些控件&#xff0c;那比较简单的方式就是把查询的数据集&#xff0c;逐条赋给对象模型&#xff0c;再加到List&#xff0c;最后在控制加到 ViewBag&#xff0c;视图循环显示ViewBag变量 控制器代码 List<Users> list new Li…...

C语言第二十弹---指针(四)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 指针 1、字符指针变量 2、数组指针变量 2.1、数组指针变量是什么&#xff1f; 2.2、数组指针变量怎么初始化 3、⼆维数组传参的本质 4、函数指针变量 4.1…...

常用排序算法(Java版本)

1 引言 常见的排序算法有八种&#xff1a;交换排序【冒泡排序、快速排序】、插入排序【直接插入排序、希尔排序】、选择排序【简单选择排序、堆排序】、归并排序、基数排序。 2 交换排序 所谓交换&#xff0c;就是序列中任意两个元素进行比较&#xff0c;根据比较结果来交换…...

CPP项目:Boost搜索引擎

1.项目背景 对于Boost库来说&#xff0c;它是没有搜索功能的&#xff0c;所以我们可以实现一个Boost搜索引擎来实现一个简单的搜索功能&#xff0c;可以更快速的实现Boost库的查找&#xff0c;在这里&#xff0c;我们实现的是站内搜索&#xff0c;而不是全网搜索。 2.对于搜索…...

【洛谷 P1616】疯狂的采药 题解(动态规划+完全背包)

疯狂的采药 题目背景 此题为纪念 LiYuxiang 而生。 题目描述 LiYuxiang 是个天资聪颖的孩子&#xff0c;他的梦想是成为世界上最伟大的医师。为此&#xff0c;他想拜附近最有威望的医师为师。医师为了判断他的资质&#xff0c;给他出了一个难题。医师把他带到一个到处都是草…...

L1-027 出租分数 20

下面是新浪微博上曾经很火的一张图&#xff1a; 一时间网上一片求救声&#xff0c;急问这个怎么破。其实这段代码很简单&#xff0c;index数组就是arr数组的下标&#xff0c;index[0]2 对应 arr[2]1&#xff0c;index[1]0 对应 arr[0]8&#xff0c;index[2]3 对应 arr[3]0&…...

51单片机精进之路-1点亮led灯

本例中led灯使用共阳极连接在电路中&#xff0c;共阳极即将led的正极接在一起&#xff0c;通过上拉电阻接到电源正极&#xff0c;通过单片机io与Led的负极相连&#xff0c;io输出低电平&#xff0c;有电流从led流过&#xff0c;此时led点亮&#xff0c;当io输出高电平时&#x…...

嵌入式学习Day14 C语言 --- 位运算

位运算 注意&#xff1a;符号位也遵循这个规则 一、按位与(&) 运算规则&#xff1a;一假则假 int a 0x33;a & 0x55;0011 00110101 0101 &----------0001 0001 //0x11 二、按位或(|) 运算规则&#xff1a;一真则真 int a 0x33;a |0x55;0011 00110101 0101 |…...

idea设置terminal为git

要在IntelliJ IDEA中设置终端为Git Bash&#xff0c;请按照以下步骤操作&#xff1a; 打开 Settings&#xff08;设置&#xff09;。点击 Tools&#xff08;工具&#xff09;选项卡。进入 Terminal&#xff08;终端&#xff09;界面。在 Shell Path 下选择 Browse&#xff08;…...

《MySQL 简易速速上手小册》第3章:性能优化策略(2024 最新版)

文章目录 3.1 查询优化技巧3.1.1 基础知识3.1.2 重点案例3.1.3 拓展案例 3.2 索引和查询性能3.2.1 基础知识3.2.2 重点案例3.2.3 拓展案例 3.3 优化数据库结构和存储引擎3.3.1 基础知识3.3.2 重点案例3.3.3 拓展案例 3.1 查询优化技巧 让我们来聊聊如何让你的 MySQL 查询跑得像…...

【golang】23、gorilla websocket 源码:examples、数据结构、流程

文章目录 一、examples1.1 echo1.1.1 server.go1.1.2 client.go 1.2 command1.2.1 功能和启动方式1.2.2 home.html1.2.3 main.go 1.3 filewatch1.3.1 html1.3.2 serveHome 渲染模板1.3.3 serveWs1.3.4 writer() 1.4 buffer pool1.4.1 server1.4.2 client 1.5 chat1.5.1 server1…...

SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式 基础(持续更新~)

具体操作&#xff1a; day2: 作用&#xff1a; 出现跨域问题 配相对应进行配置即可解决&#xff1a; IDEA连接的&#xff0c;在url最后加参数?useSSLfalse注意链接密码是123&#xff08;docker中mysql密码&#xff09; 注意&#xff0c;虚拟机中设置的密码和ip要和主机上…...