【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.rhkb.cn/news/281480.html

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

相关文章

定时在windows上执行python脚本

现在&#xff0c;有两种方法来执行Python脚本。 第一种方法要写批处理Bat文件&#xff0c;另一种则不需要。 如果您不想写批处理文件&#xff0c; 定时在windows上执行python脚本的步骤如下&#xff1a; 1. 搜索任务计划程序(Task Scheduler) 2. 在右侧“action”窗格下&…

Redis监控工具

Redis 是一种 NoSQL 数据库系统&#xff0c;以其速度、性能和灵活的数据结构而闻名。Redis 在许多领域都表现出色&#xff0c;包括缓存、会话管理、游戏、排行榜、实时分析、地理空间、叫车、聊天/消息、媒体流和发布/订阅应用程序。Redis 数据集完全存储在内存中&#xff0c;这…

QT_day2:页面设计使用ui

1、自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面。&#xff08;不要使用课堂上的图片和代码&#xff0c;自己发挥&#xff0c;有利于后面项目的完成&#xff09; 要求&#xff1a; 1. 需要使用Ui界面文件进行界面设计 2. ui界面上的组件相关设置&#xff0c…

Python学习:元组

Python 元组概念 Python 中的元组&#xff08;tuple&#xff09;是不可变的有序集合。它是一种数据类型&#xff0c;类似于列表&#xff08;list&#xff09;&#xff0c;但在创建后不能被修改。元组使用圆括号 () 来表示&#xff0c;其中的元素可以是任意类型&#xff0c;并且…

家电工厂5G智能制造数字孪生可视化平台,推进家电工业数字化转型

家电5G智能制造工厂数字孪生可视化平台&#xff0c;推进家电工业数字化转型。随着科技的飞速发展&#xff0c;家电行业正迎来一场前所未有的数字化转型。在这场制造业数字化转型中&#xff0c;家电5G智能制造工厂数字孪生可视化平台扮演着至关重要的角色。本文将从数字孪生技术…

基于SSM的校园失物招领系统设计与实现+数据库+免费远程调试

项目介绍: 基于SSM的校园失物招领系统设计与实现。Javaee项目&#xff0c;采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringMvc Mybatis JspVuelayuiElementui来实现。MySQL数…

前端框架推荐 Arco Design

Arco Design - 企业级产品的完整设计和开发解决方案 预览地址&#xff1a;Arco Design Pro - 开箱即用的中台前端/设计解决方案 一 开发 有vue3、React版本。 文档地址&#xff1a;Arco Design - 企业级产品的完整设计和开发解决方案 还配有对应脚手架&#xff1a;GitHub -…

YOLOv3学习

YOLOv3仅使用卷积层&#xff0c;使其成为一个全卷积网络&#xff08;FCN&#xff09;。文章中&#xff0c;作者提出一个新的特征提取网络&#xff0c;Darknet-53。正如其名&#xff0c;它包含53个卷积层&#xff0c;每个后面跟随着batch normalization层和leaky ReLU层。没有池…

软件工程-第6章 面向对象方法UML

UML是一种图形化语言&#xff0c;简称画图。 6.1 表达客观事物的术语 6.2 表达关系的术语 1.关联 表达关联语义相关术语&#xff1a;关联名、导航、角色、可见性、多重性、限定符、聚合、组合。 2.泛化 3.细化 6.3 组织信息的一种通用机制-包 6.4 模型表达工具 一个用况图包含6…

对通信安全IEC 61784-3 的理解

IEC 61784-3是国际电工委员会&#xff08;IEC&#xff09;发布的关于工业通信网络和系统的标准之一&#xff0c;它规定了工业通信网络和系统中使用的数据传输协议和通信规范。IEC 61784-3主要涉及工业自动化领域中的实时以太网通信&#xff0c;包括数据传输速率、数据传输方式、…

文件包含漏洞之包含NGINX日志文件(常用)

条件&#xff1a;知道目标服务器的日志文件存贮路径&#xff0c;并且存在文件包含漏洞 首先对目标服务器发送一次含有木马的请求&#xff0c;目的是让目标服务器日志中生成含有木马的日志记录。因为发送过程中&#xff0c;使用了url编码&#xff0c;我们抓包进行更改成能够执行…

Java类的多态作用及解析

多态是面向对象编程中一个重要的特性。简单来说&#xff0c;多态就是指同一个方法在不同的对象上有不同的实现。通过多态&#xff0c;我们可以在运行时根据对象的实际类型来动态地调用相应的方法&#xff0c;从而提高代码的灵活性和可扩展性。 以下是 Java 类中多态的一些作用…

THM学习笔记—Bounty Hacker

nmap扫描&#xff0c;扫了一大堆但只有三个端口是开放的 试试ftp是否可以匿名登录 可以匿名登录&#xff0c;把里面的文件下载下来 查看里面的内容&#xff0c;猜lin为用户名&#xff0c;locks.txt为密码列表&#xff0c;使用hydra进行ssh登录。 找到密码了&#xff0c;进行ssh…

【软考】生成树

目录 1. 概念2. 图解3. 例题3.1 例题1 1. 概念 1.对于有n个顶点的连通图&#xff0c;至少有n-1条边&#xff0c;而生成树中恰好有n-1条边2.连通图的生成树是该图的极小连通子图3.若在图的生成树中任意加一条边&#xff0c;则必然形成回路4.图的生成树不是唯一的5.从不同的顶点…

【热门话题】深入浅出:npm常用命令详解与实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 标题&#xff1a;深入浅出&#xff1a;npm常用命令详解与实践引言一、npm基本概…

CentOS无法解析部分网站(域名)

我正在安装helm软件&#xff0c;参考官方文档&#xff0c;要求下载 get-helm-3 这个文件。 但是我执行该条命令后&#xff0c;报错 连接被拒绝&#xff1a; curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 # curl: (7) Fai…

LeetCode刷题记录:(13)N皇后(难题不难)

leetcode传送通道 传说中的N皇后&#xff0c;不难&#xff0c;进来了就看完吧 注释序号代表鄙人写代码的顺序和思考逻辑&#xff0c;供参考 class Solution {// 1.定义结果数组List<List<String>> result new ArrayList<>();public List<List<String&…

若依添加页面

背景&#xff1a;我想增加的是一个收支管理的页面 views中直接添加income文件夹&#xff0c;里面放着index.vue 网页的菜单中添加这个页面的菜单

【C语言】数9的个数

编写程序数一下 1到 100 的所有整数中出现多少个数字9 1&#xff0c;首先产生1~100的数字。然猴设法得到数9个数&#xff0c;例如个位&#xff1a;19%109&#xff0c;十位&#xff1a;91/109。 2&#xff0c;每次得到数九的时候&#xff0c;就用一个变量来进行计数。 代码如…

Linux进程地址空间补充——内核空间

内核地址空间 ​ 内核空间1GB大小&#xff0c;映射的是操作系统的代码和数据&#xff1b;操作系统是计算机启动时载入的第一个程序&#xff0c;一般是靠近物理内存的低地址处&#xff1b;进程地址空间的内核空间经过内核级页表与物理内存建立映射&#xff0c;当然也有偏移量计…