【Linux】Linux线程

news/2024/5/20 3:51:46

一、Linux线程的概念

1.什么是线程

1.一个进程的一个执行线路叫做线程,线程的一个进程内部的控制序列。

2.一个进程至少有一个执行线程

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

4.操作系统将进程虚拟地址空间的资源分配给每个执行流,就成了线程执行流。

2.线程的优点

1.创建新线程的成本远低于创建新进程。

2.与进程切换相比,线程切换需要较少的操作系统资源。

3.线程比进程占用更少的资源。

4.它们能有效地利用多处理器并行处理的能力。

5.在等待缓慢的I/O操作时,程序还可以进行其他计算任务。

6.在多处理器系统中,计算密集型应用通过分解计算任务到多个线程中来运行。

7.I/O密集型应用通过重叠I/O操作来提升性能,允许线程同时等待多个I/O操作。

3.线程的缺点

a.增加额外开销

计算密集型线程,若很少受外部事件阻塞,通常不能与其他线程共享同一处理器。当计算密集型线程的数量超过可用处理器时,可能会导致显著的性能损失。这种性能损失主要是由于增加了额外的同步和调度开销,而可用资源保持不变。

b.线程安全

多线程编程需要更全面和深入的考量。在多线程程序中,由于时间分配上的微小偏差或共享了不应共享的变量,可能会产生不良影响,这意味着线程之间缺乏必要的保护。

c.缺乏访问控制

进程是访问控制的基本单位。在一个线程中调用某些操作系统函数可能会影响整个进程。

d.编程难度提高

编写和调试多线程程序比单线程程序更加困难。

e.线程异常

如果单个线程出现除零错误或野指针问题导致崩溃,整个进程也会因此崩溃。线程是进程的执行分支,一旦线程发生异常,就相当于进程发生异常,这将触发信号机制并终止进程。一旦进程终止,该进程内的所有线程也将随之退出。

二、Linux下进程与线程的区别

进程是资源分配的基本单位 线程是调度的基本单位

1.资源的区别

线程共享进程的数据,同时也有它们自己独立的数据部分:

线程ID

一组寄存器

errno

信号屏蔽字

调度优先级

进程中的多个线程共享同一个地址空间,因此Text Segment、Data Segment都是共享的。如果定义了一个函数,它可以在所有线程中被调用;如果定义了一个全局变量,它也可以在所有线程中被访问。除此之外,所有线程还共享其他进程资源和环境:

文件描述符表

每种信号的处理方式

当前工目录

用户id和组id

2.切换开销

从一个进程切换到另一个进程,需要保存当前进程的状态并恢复新进程的状态,这个过程称为上下文切换。而线程切换的开销较小,因为线程共享相同的地址空间,不需要改变地址空间的映射关系。

3.同步与通信

进程之间的通信需要进行进程间通信(IPC),如管道、消息队列等,这些方法效率较低。而线程之间的通信可以直接通过共享内存、全局变量等方式进行,效率较高。

4.单元性

进程是独立的执行单位,一个进程崩溃不会影响其他进程。而线程共享同一个进程的资源,一个线程崩溃可能导致整个进程崩溃。

三、Linux线程控制

1.POSIX线程库

POSIX线程库(POSIX Threads)也被称为pthread库,是一套线程创建、同步、调度和管理的标准接口,定义了在多线程程序中使用线程的API和语义。POSIX线程库通常用于UNIX和类UNIX操作系统(如Linux)上。

POSIX线程库的主要特点包括:

  1. 标准化接口: POSIX线程库定义了一套标准的线程操作接口,包括线程的创建、销毁、同步等操作,使得多线程程序能够以跨平台的方式编写。

  2. 线程管理: POSIX线程库提供了对线程的管理功能,包括线程创建、退出、同步、互斥锁、条件变量、信号量等。

  3. 线程调度: POSIX线程库定义了线程的调度接口,可以设置线程的调度策略、优先级和调度参数。

  4. 线程同步: 提供了各种线程同步的机制,确保多个线程之间的协作和同步。

在使用POSIX线程库时,需要包含 <pthread.h> 头文件,并链接 -lpthread 库。

2.创建线程

使用pthread_create()函数

函数声明:

参数解析

thread:返回线程ID

attr:设置线程的属性,attr为NULL表示使用默认属性

start_routine:是个函数地址,线程启动后要执行的函数

arg:传给线程启动函数的参数

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

错误检查:

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

而pthreads函数在出错时不会设置全局变量errno(尽管大多数其他POSIX函数会这样做),它们会通过返回值来传递错误代码。

pthreads也提供了线程内的errno变量,以便支持其他依赖errno的代码。但对于pthreads函数的错误处理,建议通过检查返回值来判断,因为这比读取线程内的errno变量要节省开销。

示例代码:

#include <iostream>
#include <unistd.h>
#include <pthread.h> 
#include "Thread.hpp"void *Printf(void *arg)
{printf("wohu!, 我是一个线程...\n");sleep(2);   }using namespace std;
int main()
{pthread_t pid;int ret = pthread_create(&pid, nullptr, Printf, nullptr);if(ret != 0){cerr << " 线程创建错误" << endl;exit(EXIT_FAILURE);}pthread_join(pid,nullptr);return 0;
}

3、线程ID与虚拟地址空间的关系

pthread_create函数生成一个线程ID,存储在第一个参数所指的地址。这个线程ID与之前提到的不同。

先前提到的线程ID是进程调度的一部分。由于线程是轻量级的进程,是操作系统调度的基本单位,因此它需要一个唯一的数值来标识。

pthread_create函数的第一个参数指向一个虚拟内存单元,这个内存单元的地址就是新线程的线程ID,这属于NPTL线程库的范围。线程库的操作都是基于这个线程ID进行的。

NPTL线程库还提供了pthread_self函数,用于获取线程自身的ID。

对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

4.线程终止

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

1. 从线程函数中返回。这种方法不适用于主线程,因为从main函数中返回等同于调用exit。

2. 线程可以通过调用pthread_exit来结束自己。

3. 一个线程可以通过调用pthread_cancel来终止同一进程中的另一个线程。

5.线程等待

为什么需要线程等待?

已退出的线程,其空间未被释放,仍在进程地址空间内。

新创建的线程将不会复用之前退出线程的地址空间。

使用pthread_join来进行线程等待

调用该函数的线程会挂起等待,直到标识为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线程的终止状态,可以向value_ptr参数传递NULL值。

6.线程分离

默认情况下,新创建的线程是可连接的。线程结束后,必须执行pthread_join操作以释放资源,否则可能导致系统资源泄露。如果不需要线程的返回值,join操作可能显得多余。在这种情况下,我们可以设置线程在退出时自动释放其资源。

使用pthread_detach进行线程分离

int pthread_detach(pthread_t thread);

也可以自己分离

pthread_detach(pthread_self());

四、Linux线程互斥

1.线程互斥的前置概念

临界资源:指多线程执行流中共享的资源。

临界区:是指在每个线程中访问临界资源的那部分代码。

互斥:确保任何时刻只有一个执行流能进入临界区访问临界资源,起到保护临界资源的作用。

原子性:指的是不可被任何调度机制中断的操作,这种操作只有完成或未完成两种状态。

2.互斥锁mutex

多个执行流同时访问同一个临界资源会带来一些问题,这个时候就需要用到锁。

2.1比如说下面这个简单的卖票场景:

四个线程同时卖100张票

#include <iostream>
#include <unistd.h>
#include <pthread.h> 
#include "Thread.hpp"
int t = 100;void *tick(void *arg)
{while(1){if(t < 0){break;}else{usleep(1000);printf("线程%d卖: %d\n", pthread_self(), t);t--;}}return nullptr;
}int main()
{pthread_t t1,t2,t3,t4;pthread_create(&t1, nullptr, tick, nullptr);pthread_create(&t2, nullptr, tick, nullptr);pthread_create(&t3, nullptr, tick, nullptr);pthread_create(&t4, nullptr, tick, nullptr);pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);pthread_join(t4,nullptr);return 0;
}

运行结果:

为什么会出现这样的情况?

1.if 语句判断条件为真以后,代码可以并发的切换到其他线程

2.usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段

3.t-- 操作本身就不是一个原子操作

t--的汇编代码

可以看到t--是由三条汇编代码组成的,所以t--不是原子的。

要解决上述问题,需确保三个条件得到满足:

1.代码必须实现互斥,当一个线程进入临界区执行时,其他线程不得进入此临界区。

2.若多个线程同时请求执行临界区代码,而且临界区内没有线程在执行,则只能让一个线程进入该临界区。

3.若线程未在临界区内执行,则不应阻止其他线程进入临界区。

本质上就是加锁。

2.2互斥锁的接口

2.2.1初始化mutex

1.静态初始化

把mutex定义到全局区

2.使用init函数

2.2.2销毁mutex

使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁

2.2.2mutex加锁和解锁

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

调用 pthread_ lock 时,可能会遇到以下情况:

当互斥量处于未锁定状态时,该函数会锁定互斥量,并返回成功。

如果在调用函数时,互斥量已被其他线程锁定,或者有其他线程也在申请互斥量但未能成功竞争到,那么pthread_mutex_lock调用将会阻塞(即执行流被挂起),直到互斥量被解锁。

改进之前的代码:

加上锁

#include <iostream>
#include <unistd.h>
#include <pthread.h> 
#include "Thread.hpp"
int t = 100;pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;void *tick(void *arg)
{while(1){ pthread_mutex_lock(&mtx);if(t < 0){pthread_mutex_unlock(&mtx);break;}else{usleep(1000);printf("线程%d卖: %d\n", pthread_self(), t);t--;pthread_mutex_unlock(&mtx);}}return nullptr;
}int main()
{pthread_t t1,t2,t3,t4;pthread_create(&t1, nullptr, tick, nullptr);pthread_create(&t2, nullptr, tick, nullptr);pthread_create(&t3, nullptr, tick, nullptr);pthread_create(&t4, nullptr, tick, nullptr);pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);pthread_join(t4,nullptr);return 0;
}

运行结果:


http://www.mrgr.cn/p/41446547

相关文章

自定义拦截器jwt登录校验接口模拟账号登录

五一闲在宿舍&#xff0c;本来想写一个自己的简易博客网站&#xff0c;发现vue基础太差&#xff0c;做不出来页面效果于是便放弃&#xff0c;但也没有完全放弃。于是我分析了一下简易博客的后端实现流程&#xff0c;除了最基本的crud以外&#xff0c;在自己目前的对接口的分析中…

Qt---day2-信号与槽

1、思维导图 2、 拖拽式 源文件 #include "mywidget.h" #include "ui_mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //按钮2 this->btn2new QPushButton("按钮2",th…

Java的Fork-Join简单介绍

Java的Fork-Join框架是Java 7引入的一个用于并行处理的轻量级框架&#xff0c;它基于分治策略&#xff08;Divide and Conquer&#xff09;&#xff0c;特别适合于那些可以被分解为多个子任务的任务。Fork-Join框架的核心思想是将一个大任务&#xff08;Task&#xff09;拆分成…

Sermant在异地多活场景下的实践

本文将对Sermant在异地多活场景下的实践进行剖析。本文分享自华为云社区《Sermant在异地多活场景下的实践》,作者:华为云开源。 Sermant社区在1.3.0和1.4.0版本相继推出了消息队列禁止消费插件和数据库禁写插件,分别用于解决异地多活场景下的故障切流和保护数据一致性问题。…

算法~PBKDF2-SHA让密码更安全

摘要:在当今的数字世界中,密码安全是至关重要的。为了保护用户密码免受未经授权的访问和破解,Password-Based Key Derivation Function 2 (PBKDF2)算法成为了一种重要的工具。 在 PBKDF2 算法中,SHA 表示 Secure Hash Algorithm,它是一系列密码哈希函数的标准,其中 SHA-1…

C#语言核心

一、面向对象基本概念 万物皆对象&#xff0c;用程序来抽象&#xff08;形容&#xff09;对象&#xff0c;用面向对象的思想来编程 用中文去形容一类对象&#xff0c;把一类对象的共同点提取出来&#xff0c;然后用程序语言把它翻译过来&#xff0c;带着对象的概念在程序中使…

P9218 Apollo题解

题目分析 仔细阅读题目,可知题目要求的是对于每个 \(a_i\) 的 \(\sum\limits_{j=1}^ng(a_i,a_j)\) 。再结合 \(g(a,b)\) 的定义,可知,对于 \(a_i\) 来说,我们需要计算 \(a_i\) 与 \(a_1\sim a_n\) 构成的 \(n\) 组数对的 \(g(a_i,a_j)\) 的总和。对于 \(g(a,b)\) 的值,则是…

tomcat的cookie报错

1.File–>project structure -> Modules(+)–>java—>选择servlet-api.jar 需要添加servlet.api.jar包依赖

SpringBoot中这样用ObjectMapper

每次new一个单例化个性化配置小结 你要说他有问题吧&#xff0c;确实能正常执行&#xff1b;可你要说没问题吧&#xff0c;在追求性能的同学眼里&#xff0c;这属实算是十恶不赦的代码了。 首先&#xff0c;让我们用JMH对这段代码做一个基准测试&#xff0c;让大家对其性能有个…

[转帖]如何通过Native Memory Tracking追踪JVM的内部内存使用?

https://zhuanlan.zhihu.com/p/368599144# 导读:Java8给 HotSpot VM引入了Native Memory Tracking (便于阅读我统一简称为NMT)特性,可以用于追踪JVM的内部内存使用,并可以通过jcmd命令来访问。不过要注意的是NMT是通过在JVM代码中添加跟踪点的方式实现内存跟踪的,因此NMT不…

github提交不了的问题

开了VPN提交的时候提示这个报错 是需要这两个端口号一致&#xff0c;就能提交了

无人零售,重塑购物新纪元

在这个快节奏的时代&#xff0c;科技的每一次跃进都在悄无声息地改变着我们的生活方式。而今&#xff0c;无人零售正以雷霆之势&#xff0c;颠覆传统购物模式&#xff0c;为我们带来前所未有的便捷与智能体验。想知道无人零售如何彻底改变我们的购物方式吗&#xff1f;跟随我&a…

Vue3管理系统-路由设置+表单校验

一、配置路由规则 1.在views 下创建文件夹分类,搭好架子 2.配置路由规则 在router下Index.js import { createRouter, createWebHistory } from vue-routerconst router createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [//一级路由//这里可以…

栈数据结构

1,概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈 顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈(push)&#x…

set-cookie字段,cookie文件介绍+原理,如何查看cookie文件,在基于http协议服务器的代码实现,cookie存在问题+解决(会话机制)

目录 Set-Cookie 引入 介绍 原理 描述 图解 保存"cookie文件"的方法 内存级 文件级 查看cookie文件 示例 实现 介绍 代码 核心代码 全部代码 示例 cookie存在的问题 介绍 存在的必要性 如何解决 问题梳理 引入 会话机制 -- 解决信息泄漏…

微软正在自主构建一个名为 MAI-1 的大型语言模型(不依赖 OpenAI)

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

栈的实现以及c语言解决括号匹配问题

一、栈的实现 1、头文件 typedef int STDataType; typedef struct Stack {STDataType* _a;int _top; // 栈顶int _capacity; // 容量 }Stack; // 初始化栈 void StackInit(Stack* ps); // 入栈 void StackPush(Stack* ps, STDataType data); // 出栈 void StackPop(S…

于光电容积波PPG和心电图ECG的连续血压估计深度学习模型

具体的软硬件实现点击 http://mcu-ai.com/ MCU-AI技术网页_MCU-AI人工智能 血压监测是监测人们健康状况的途径之一。早期发现血压异常可以帮助患者得到早期治疗并降低与心血管疾病相关的死亡率。因此,有一种机制来实时监测患者的血压变化是非常有价值的。在本文中,我们提出了…

向各位请教一个问题

这是菜鸟上的一道题目&#xff0c;单单拿出来问问大家&#xff0c;看看能不能解惑 &#xff0c;谢谢各位&#xff01; 题目25&#xff1a;求12!3!...20!的和 解题思路&#xff1a;这个题不知道为什么我用DEV C 5.11显示出来为0.000000&#xff0c;可能版本有问题&#xff1f;&a…