【Linux】多线程编程基础

在这里插入图片描述

💻文章目录

  • 📄前言
  • 🌺linux线程基础
    • 线程的概念
      • 线程的优缺点
      • 线程与进程的区别
    • 线程的创建
  • 🌻linux线程冲突
    • 概念
    • 互斥锁函数介绍
    • 加锁的缺点
  • 📓总结


📄前言

无论你是否为程序员,相信多线程这个词汇应该都有所耳闻,像是在某个优化很差的游戏中听闻这游戏甚至是单线程的,如果你对多线程感兴趣,不妨点进本文来学习多线程编程,即使没有深厚的C/C++编程基础,你也能到本文学习到如何编写多线程程序。

🌺linux线程基础

线程的概念

线程指的是系统中的执行路径,每个线程都线程系统中的一切进程都至少有一个线程,它们共享同一个进程.

其实在linux中,实际并没有真正的线程,线程通常被称为轻量级进程(LWP),这是因为在linux的实现中,线程和进程并没有什么本质的区别,只是线程被设计得更加轻量,以便更高效实现并发执行。

线程pcbtask_struc   -+                        +-------------------++--------+    |                        |    内核映射区域     ||        |    |                        +-------------------++--------+    |                        |||                        +-------------------+task_struc    |                        |                   |+--------+    |                        |      共享库        ||        |    |                        |                   |+--------+    |                        +-------------------+|                        ||task_struc    |    指向同一地址空间      +-------------------++--------+    |-------------------+>   |      数据段        ||        |    |                        +-------------------++--------+    |                        |   未初始化数据区    ||                        +-------------------+task_struc    |                        |   已初始化数据区    |+--------+    |                        +-------------------+|        |    |                        |      代码段        |+--------+   -+                        +-------------------+

线程的优缺点

  • 优点:
  1. 共享资源:在同一线程的线程共享着大部分内存空间,如:代码段、数据段、文件描述符、堆、共享内存区等。这使得线程间通信非常地高效,无需IPC机制开销。
  2. 独立调度:虽然线程中大部分地址空间都与主线程共享,但线程也有自己的一部分数据,如:栈与寄存器状态,这使得他们可以独立于其他线程运行。
  3. 响应性:在多线程程序中,一个进程的阻塞不会影响到其他进程。
  4. 资源利用率:多线程可以提高在多核处理器上运行的效率,实现并行执行。
  • 缺点:
  1. 编程困难:因为多线程需要考虑到临界区、互斥、同步等问题,所以对程序员的代码能力要求较高。
  2. 同步复杂性:多线程的资源共享需要谨慎处理,否则会出现数据二义性问题。
  3. 调试困难: 多线程调试一直都是令人头疼的问题,因为bug可能会难以复现,并且不是所有调试工具都支持多线程调试。
  4. 健壮性:如果任意一个线程触发了异常,则整个程序都会终止。

线程与进程的区别

  1. 定义:进程是资源分配的最小单位,线程则是cpu调度执行的最小单位。
  2. 资源共享:进程之间资源独立,同一进程内的线程共享进程资源。
  3. 创建开销:线程的创建和切换开销都小于进程,因为线程之间资源共享。

线程的创建

  • 函数接口介绍:头文件:<pthread.h>
  1. 创建线程
// 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);// pthread_t 是 POSIX 线程(Pthreads)库中定义的一个数据类型,用于唯一标识一个线程
  • 参数
    • thread: 线程
    • attr: 指定线程属性的指针,可设为NULL
    • start_routine:线程开始执行的函数
    • arg: start_routine 函数的参数
  1. 回收线程
// 等待线程结束并回收线程的资源,防止类似“僵尸进程”的情况
int pthread_join(pthread_t thread, void** retval);
  • 参数:
    • thread:用于回收的线程id;
    • retval:用于存储线程的返回值。
  1. 退出线程:
// 用于终止当前的线程,因为exit会终止整个进程,所以有了这个函数
void pthread_exit(void* retval);
  • 参数:
    • retval:退出线程时返回的值
  1. 分离线程:
// 如果觉得join操作是一种负担的时候,可以使用pthread_detach
// 用于分离线程,当线程结束时,自动回收线程资源。
int pthread_detach(pthread_t thread);
  • 参数:
    • thread:分离的线程id

介绍完了函数接口,就到实践的时间啦。

  • 使用函数
#include <pthread.h>
#include <iostream>void *thread_func(void *arg)
{// 获取当前线程的tidstd::cout << "Thread" << (char *)arg << " id:" << gettid() << " started" << std::endl;int cnt = 10;while (cnt >= 0){std::cout << "Thread" << (char *)arg << " id:" << gettid() << " is running, cnt = " << cnt << std::endl;cnt--;sleep(1);}// 子线程退出pthread_exit(nullptr); // 可有可无
}void *func_test(void* args)
{printf("I LOVE LINUX\n");pthread_detach(pthread_self());	// 使用pthread_self()可以使子线程自己分离。return nullptr;
}int main()
{pthread_t thread, thread2;pthread_create(&thread, nullptr, thread_func, (void *)"-1");pthread_create(&thread2, nullptr, func_test, nullptr);// 主线程等待子线程结束pthread_join(thread, NULL); // 回收线程return 0;
}

🌻linux线程冲突

概念

多线程的高效率也是存在着代价的,当多个线程同时访问一份资源时,就会发生线程冲突(数据二义性),我们一般将这些多个线程都要访问的资源称为临界区

要探讨数据二义性问题,就得从汇编代码开始讲解

; 例如一个简单的++操作,看似只做了一个操作,但在汇编中却并不是这样。MOV EAX, [x]   ; 将x的值加载到EAX寄存器
INC EAX        ; 将EAX寄存器的值增加1		
MOV [x], EAX   ; 将修改后的值存回内存位置x; 多个线程同时访问这个资源(x),当线程1在将x放入寄存器EAX时,线程2可能就已经将x++,并改变了内存的数值
; 线程1将寄存器的值++后,又放回了x的内存。建议使用vs2022 进行反汇编调试来观看现象。
  • 线程冲突演示
#include <pthread.h>
#include <iostream>int x = 0;void *func(void *args)
{for (int i = 0; i < 100000000; i++)	//数值越大,冲突概率越大++x;pthread_exit(nullptr);
}int main()
{// 线程冲突演示pthread_t pid1, pid2;pthread_create(&pid1, nullptr, func, nullptr);pthread_create(&pid2, nullptr, func, nullptr);pthread_join(pid1, nullptr);	//回收线程pthread_join(pid2, nullptr);cout << "x = " << x << endl;return 0;
}
// 结果:
// x = 154698688

解决方案: 为了解决这种情况,就得当线程访问临界区资源时限制为一个线程访问,也就是说,需要给线程加锁。

互斥锁函数介绍

  1. 创建锁
// 初始化锁 pthread_mutex_t 用于声明互斥量(mutex)对象。// 静态加锁 (全局变量或静态进行初始化)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 动态初始化
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
  1. 线程加锁
// 给线程加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 参数:
    • mutex:指向互斥锁对象
  1. 互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 参数:
    • mutex:指向需要解锁的互斥锁对象的指针。

注意:加锁操作本身时原子性的,所以不用担心锁的二义性。

  • 互斥锁的使用
int x = 0;
// 初始化锁对象
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *func(void *args)
{for (int i = 0; i < 100000000; i++){pthread_mutex_lock(&mutex);	//加锁++x;pthread_mutex_unlock(&mutex);	//解锁}pthread_exit(nullptr);
}int main()
{// 线程冲突演示pthread_t pid1, pid2;pthread_create(&pid1, nullptr, func, nullptr);pthread_create(&pid2, nullptr, func, nullptr);pthread_join(pid1, nullptr);pthread_join(pid2, nullptr);cout << "x = " << x << endl;return 0;
}

加锁的缺点

如果我们尝试运行程序,会发现加锁后的运行速度明显慢了不少。锁的使用会增加性能的开销,而且线程可能会变成串行执行,为了避免多余的性能开销,每次使用锁都应该避免将非临界区的资源加锁。

在一些特殊的情况下,可能会

  • 死锁演示:
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;void *func(void *args)
{int* cnt = (int*)args;if(*cnt <= 0)	return nullptr;pthread_mutex_lock(&mtx);		// 第二次递归时等待着线程解锁std::cout << "func()" << std::endl;	--(*cnt);func(args);		// 递归进入下一层,但锁还没解锁。pthread_mutex_unlock(&mtx);	// 程序永远走不到这里。return nullptr;
}int main()
{// 线程冲突演示pthread_t pid;int* cnt = new int(10);pthread_create(&pid, nullptr, func, (void*)cnt);pthread_join(pid, nullptr);return 0;
}

📓总结

多线程编程
优点缺点
资源共享线程间共享进程资源(如代码段、数据段、文件描述符等),使得线程间通信非常高效,无需通过IPC机制开销。多线程的资源共享需要通过同步机制(如互斥锁)来管理,否则可能导致数据不一致或竞争条件的问题。
独立调度线程可以独立于其他线程运行,拥有自己的执行路径。这增加了应用程序的响应性和处理效率。线程调度引入了上下文切换的开销,尤其是在高度竞争的环境中,可能降低整体性能。
效率提升在多核处理器上,多线程能够利用额外的核心执行更多的任务,提高了程序的执行效率和资源利用率。编写高效的多线程程序需要深入理解并发、同步等概念,增加了开发的复杂度。

多线程编程是一把双刃剑,使用多线程能够显著提升程序的性能,但它也为程序带来了许多潜在的风险,在处理器核心数越来越多的当今,学习多线程也变得越发重要,希望本文对你的学习有所帮助。

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

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

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

相关文章

量子计算机

近日&#xff0c;在AWS re&#xff1a;Invent全球大会上&#xff0c;亚马逊官宣AWS三箭齐发量子计算组合拳&#xff1a;Braket、AWS量子计算中心和量子解决方案实验室。 随着亚马逊的强势入局&#xff0c;加上此前鼓吹量子霸权的谷歌、起步最早的IBM、暗自发力的微软&#xff…

react-jsx

react04 jsx语法 - 01 基础知识&#xff1a; jsx javascript xml(html) 把js和heml标签混合到一起 react视图编写及构建的简要流程 &#xff1a; 如何在react中使vs code支持格式化和快捷键提示&#xff1a;1, 2,修改文件后缀为jsx&#xff0c;因为webpack的打包规则中可以…

如何通过idea搭建一个SpringBoot的Web项目(最基础版)

通过idea搭建一个SpringBoot的Web项目 文章目录 通过idea搭建一个SpringBoot的Web项目一、打开idea&#xff0c;找到 create new project二、创建方式三、配置项目依赖四、新建项目模块五、总结 一、打开idea&#xff0c;找到 create new project 方式1 方式2 二、创建方式 新…

马斯克开源Grok-1

Grok-1是由马斯克AI创企xAI发布的第一代大语言模型&#xff0c;它以其巨大的参数量——高达3140亿&#xff0c;引起了全球范围内的广泛关注。这一参数量远超其他知名模型&#xff0c;如OpenAI的GPT-3.5&#xff0c;后者仅有1750亿参数。在2024年3月17日&#xff0c;马斯克宣布将…

【jvm】jinfo使用

jinfo介绍 jinfo 是一个命令行工具&#xff0c;用于查看和修改 Java 虚拟机&#xff08;JVM&#xff09;的配置参数。它通常用于调试和性能调优。 使用 jinfo 命令&#xff0c;你可以查看当前 JVM 的配置参数&#xff0c;包括堆大小、线程数、垃圾回收器类型等。此外&#xf…

天翼云防火墙配置端口转换案例

环境: 天翼云 云墙 问题描述: 天翼云防火墙配置端口转换案例 云主机192.168.10.9:2231 解决方案: 1.先登入云墙 可以从控制中心登入不用再输入密码 2.新建对象和端口 192.168.10.9:2231 3.到弹性IP这选个公网IP 记住弹性IP和后面虚拟IP 4.新建 目的NAT,按原有复制…

【Arxml专题】-29-使用Cantools将CAN Matrix Arxml自动生成C语言代码

目录 1 安装Python和Cantools 1.1 查看Python已安装的Package包 1.2 在Python中安装Cantools插件包 1.3 获取更多Cantools工具的更新动态 2 CAN Matrix Arxml自动生成C语言代码 2.1 批处理文件CAN_Matrix_Arxml_To_C.bat内容说明 2.2 CAN Matrix Arxml文件要求 2.3 如何…

20232831 2023-2024-2 《网络攻防实践》第3次作业

目录 20232831 2023-2024-2 《网络攻防实践》第3次作业1.实验内容2.实验过程&#xff08;1&#xff09;动手实践tcpdump&#xff08;2&#xff09;动手实践Wireshark&#xff08;3&#xff09;取证分析实践&#xff0c;解码网络扫描器&#xff08;listen.cap&#xff09; 3.学习…

react拖拽react-beautiful-dnd,一维数组,二维数组

写在前边&#xff0c;二维数组可以拖拽&#xff0c;但是不可以编辑拖拽&#xff0c;如果想要实现编辑拖拽&#xff0c;还是需要转换成一维数组。原因是因为插件的官方规定&#xff0c;在拖拽过程中不可以编辑Droppable层的Props。 相关地址&#xff1a; 中文文档地址 react-be…

VUE中添加视频播放功能

转载https://www.cnblogs.com/gg-qq/p/10782848.html 常见错误 vue-video-player下载后‘vue-video-player/src/custom-theme.css‘找不到 解决方法 卸载原来的video-play版本 降低原来的版本 方法一 npm install vue-video-player5.0.1 --save 方法二 或者是在pack.json中直…

OpenGL学习笔记【4】——创建窗口

一、前三章节的前情回顾 章节一&#xff1a;上下文(Context) OpenGL学习笔记【1】——简介-CSDN博客 章节一讲述了OpenGL在渲染的时候需要一个Context来记录了OpenGL渲染需要的所有信息和状态&#xff0c;可以把上下文理解成一个大的结构体&#xff0c;它里面记录了当前绘制使…

JVM垃圾回收之内存分配,死亡对象判断方法

Java 堆是垃圾收集器管理的主要区域&#xff0c;因此也被称作 GC 堆。 堆划分为新生代 老生代 永久代。 下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代&#xff0c;中间一层属于老年代&#xff0c;最下面一层属于永久代。 内存分配原则 对象优先在Eden区域分…

基于PID控制器的四旋翼无人机控制系统的simulink建模与仿真,并输出虚拟现实动画

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1四旋翼无人机的动力学模型 4.2 PID控制器设计 4.3 姿态控制实现 4.4 VR虚拟现实动画展示 5.完整工程文件 1.课题概述 基于PID控制器的四旋翼无人机控制系统的simulink建模与仿真,并输出vr虚拟现实…

Chronicles 是什么数据库

可以理解的是 Chronicles 是 EPIC 公司根据 IRIS 进行魔改后的一个 DBMS。 简单的来说 Chronicles 就是一个数据库管理系统&#xff0c;但这个数据库管理系统不是我们常说的关系数据库的管理系统。 数据库结构 只要对数据库有所了解的都知道数据库通常就是 2 个部分&#xf…

10W字解析 SpringBoot技术内幕文档,实战+原理齐飞,spring事务实现原理面试

第3章&#xff0c;Spring Boot构造流程源码分析&#xff0c;Spring Boot的启动非常简单&#xff0c;只需执行一个简单的main方法即可&#xff0c;但在整个main方法中&#xff0c;Spring Boot都做了些什么呢&#xff1f;本章会为大家详细讲解Spring Boot启动过程中所涉及的源代码…

会声会影2023新版本特点以及会声会影2023序列号注册机keygen下载

会声会影简介 虽然现在已经是2024年了&#xff0c;但是大家对会声会影2024的热爱一直不减&#xff0c;很多人后台问我&#xff0c;有没有会声会影2023序列号和注册机&#xff0c;这不&#xff0c;今天这篇文章它来了。 会声会影2023新版特性 1.全新的进入/中场/退出标题动态功…

【Godot 4.2】常见几何图形、网格、刻度线点求取函数及原理总结

概述 本篇为ShapePoints静态函数库的补充和辅助文档。ShapePoints函数库是一个用于生成常见几何图形顶点数据&#xff08;PackedVector2Array&#xff09;的静态函数库。生成的数据可用于_draw和Line2D、Polygon2D等进行绘制和显示。因为不断地持续扩展&#xff0c;ShapePoint…

基于Springboot的在线投稿系统+数据库+免费远程调试

项目介绍: Javaee项目&#xff0c;springboot项目。采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringBoot Mybatis VueMavenLayui来实现。MySQL数据库作为系统数据储存平台&a…

计算机组成原理 双端口存储器原理实验

一、实验目的 1、了解双端口静态随机存储器IDT7132的工作特性及使用方法 2、了解半导体存储器怎样存储和读出数据 3、了解双端口存储器怎样并行读写&#xff0c;产生冲突的情况如何 二、实验任务 (1)按图7所示&#xff0c;将有关控制信号和和二进制开关对应接好&#xff0c;…

工控机丨丨工业电脑丨工控计算机丨工业一体机丨什么是工业一体机

工业一体机俗称工控机&#xff0c;是一种专门为工业应用而设计的计算机设备&#xff0c;主要应用于工厂、车间、仓库等工业场所。此外工控机还叫做工控计算机&#xff0c;通常采用工业级主板、工业级CPU、工业级硬盘、工业级内存和工业级电源等硬件组件&#xff0c;以确保其在高…