【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,一经查实,立即删除!

相关文章

How to install Miniconda on ubuntu 22.04

How to install Miniconda on ubuntu 22.04 介绍安装脚本细节 初始化脚本细节 卸载脚本细节 介绍 通常来说&#xff0c;要安装conda有以下三种安装方案&#xff1a; Miniconda Miniconda 是 conda 的免费最小安装程序。它是 Anaconda 的一个小型引导版本&#xff0c;仅​​包…

量子计算机

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

Android仿微信视频聊天本地与远程切换功能

一、xml布局 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-auto&qu…

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 二、创建方式 新…

BERT 论文阅读笔记

文章目录 前言论文阅读同类工作比较模型架构训练方式使用步骤实验结果 其他 前言 BERT是在NLP领域中第一个预训练好的大型神经网络&#xff0c;可以通过模型微调的方式应用于后续很多下游任务中&#xff0c;从而避免了下游NLP应用需要单独构建一个新的神经网络进行复杂的预训练…

合根植物。

4.合根植物 - 蓝桥云课 (lanqiao.cn) 题目描述 w星球的一个种植园&#xff0c;被分成mxn个小格子(东西方向m行&#xff0c;南北方向n列)。每个格子里种了一株合根植物 这种植物有个特点&#xff0c;它的根可能会沿着南北或东西方向伸展从而与另一个格子的植物合成为一体。 如果…

马斯克开源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 如何…

论文翻译 - Automatically Auditing Large Language Models via Discrete Optimization

Automatically Auditing Large Language Models via Discrete Optimization Abstract1 Introduction2 Related Work3 Formulating and Solving the Auditing Optimization Problem3.1 Preliminaries3.2 The auditing optimization problem Abstract 为意外行为审计大型语言模型…

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.学习…

【小程序开发】家庭 API 集合(总)

ty.home.getCurrentHomeInfo 获取当前家庭信息 需引入HomeKit&#xff0c;且在>3.0.1版本才可使用 请求参数 Object object 属性类型默认值必填说明completefunction否接口调用结束的回调函数&#xff08;调用成功、失败都会执行&#xff09;successfunction否接口调用成…

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中直…

Lucene的lukeall工具的下载和使用图解

Lucene的lukeall工具的下载和使用图解-CSDN博客 Releases DmitryKey/luke (github.com) 需要github的用户名和密码&#xff0c;没有是下载不成功的.

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

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

FPGA_AD9361

1.集成12位DAC和ADC的一款器件&#xff0c;2个输入模拟通道和2个输出模拟通道 2.• TX频段&#xff1a;47 MHz至6.0 GHz • RX频段&#xff1a;70 MHz至6.0 GHz 3.SPI配置成LVDS或CMOS接口&#xff0c;也可以还可以选择FDD&#xff08;频分双工——全双工&#xff0c;操作时需…

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

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