【Linux】Linux线程

一、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;
}

运行结果:

五、Linux线程同步

同步的概念:在确保数据安全的基础上,实现线程以特定顺序访问临界资源,以有效预防饥饿问题,这一过程称为同步。

1.条件变量

当一个线程独占地访问某个变量时,它可能会发现在其他线程改变状态之前无法进行任何操作。

例如,当一个线程试图访问一个队列并发现它为空时,它不得不等待,直到另一个线程向队列中添加了一个元素。这种情况下,就需要使用条件变量。

条件变量的初始化与销毁:

等待条件满足:

唤醒等待:

一个小试例:

#include <iostream>
#include <unistd.h>
#include <pthread.h>int cnt = 0;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void* count(void* args)
{pthread_detach(pthread_self());uint64_t i = (uint64_t)args;std::cout << "pthread: " << i << " create success" << std::endl;while(1){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);std::cout << "pthread: " << i << " , cnt: " << cnt++ << std::endl;pthread_mutex_unlock(&mutex);}}int main()
{for(uint64_t i; i < 6; i++){pthread_t tid;pthread_create(&tid, nullptr, count, (void*)i);sleep(1);}std::cout << "主线程开始控制" << std::endl;while (true){sleep(1);pthread_cond_signal(&cond);//唤醒一个等待线程,默认是第一个//pthread_cond_broadcast(&cond); // 唤醒全部等待线程std::cout << "signal one thread..." << std::endl;}return 0;
}

为什么条件变量需要互斥锁?

条件变量是线程同步的一种,如果只有一个线程,那么这个线程就会一直等待下去,所以就需要多个线程通过某一个操作,来改变条件变量的状态,让原先不满足的条件变为满足,并唤醒在等待的线程。

要让条件变得满足就需要访问临界资源,为了保护临界资源就需要加锁。

条件变量的使用规范:

//等待
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);//唤醒
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

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

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

相关文章

JWK和JWT 学习

JWK和JWT 介绍 JWK (JSON Web Key) 和 JWT (JSON Web Token) 是现代Web应用程序中用于安全通信的两个重要概念。它们都是基于JSON的&#xff0c;并且是OAuth 2.0和OpenID Connect等协议的核心组成部分。 官方文档 JWT官方网站 JWK和JWK Set的RFC文档 JWT的RFC文档 JWK (JS…

MySQL数据库基础(数据库操作,常用数据类型,表的操作)

MySQL数据库基础&#xff08;数据库操作&#xff0c;常用数据类型&#xff0c;表的操作&#xff09; 前言 数据库的操作1.显示当前数据库2.创建数据库3.使用数据库4.删除数据库 常用数据类型1.数值类型2.字符串类型3.日期类型 表的操作1.查看表结构2.创建表3.删除表 总结 前言 …

Linux文件管理指令-001

一、文件目录 1ls ls 查看文件和目录 - a 显示指定目录下所有子目录与文件&#xff0c;包括隐藏文件 - t 显示时按修改时间(很近优先)而不是按名字排序。若文件修改时间相同&#xff0c;则 按字典顺序 - R 递归式地显示指定目录的各个子目录中的文件 - r 按字母逆序或很早…

完整性验证器:迈向 Starknet 超高可扩展性的一大步

原文&#xff1a;https://www.starknet.io/en/content/the-integrity-verifier-a-leap-toward-starknet-hyperscaling&#xff1b;https://www.starknet.io/en/ecosystem/grant 编译&#xff1a;TinTinLand 核心观点 由 Herodotus 开发的完整性验证器&#xff0c;使开发者能够…

算法-排序详解

目录 前言 比较排序 选择排序 插入排序 冒泡排序 归并排序 快速排序 非比较类排序 计数排序 桶排序 基数排序 排序的稳定性 排序算法的题目 前言 计算机的工作之一就是对数据的处理&#xff0c;处理数据有一个常见的操作就是对数据排序&#xff0c;比如新闻系统总…

JWT深入浅出

文章目录 JWT深入浅出1.JWT是什么2.为什么选JWT2.1 传统Session认证2.2 JWT认证 3.JWT怎么用4. jwt绝对安全吗&#xff1f; JWT深入浅出 1.JWT是什么 JWT&#xff08;JSON Web Token&#xff09;是一种用于在网络应用间传递信息的开放标准&#xff0c;通常用于身份认证和非敏…

Unity VR在编辑器下开启Quest3透视(PassThrough)功能

现在有个需求是PC端串流在某些特定时候需要开启透视。我研究了两天发现一些坑,记录一下方便查阅,也给没踩坑的朋友一些思路方案。 先说结论,如果要打PC端或者在Unity编辑器中开启,那么OpenXR当前是不行的可能还需要一个长期的过程,必须需要切换到Oculus。当然Unity官方指…

机器人系统可以支持对接人工系统吗?

​ 随着科技的飞速发展&#xff0c;机器人系统在各行各业都扮演着越来越重要的角色。它们可以高效地处理大量数据&#xff0c;执行繁琐的任务&#xff0c;甚至在某些领域超越了人类的能力。然而&#xff0c;机器人系统也有其局限性&#xff0c;特别是在处理复杂的人际交往…

机器学习作业4——朴素贝叶斯分类器

目录 一、理论 一个例子&#xff1a; 二、代码 对于代码的解释&#xff1a; 1.fit函数&#xff1a; 2.predict函数: 三、实验结果 原因分析&#xff1a; 一、理论 朴素贝叶斯分类器基于贝叶斯定理进行分类&#xff0c;通过后验概率来判断将新数据归为哪一类。通过利用贝…

SpringCloud面试题

SpringCloud常见组件有哪些 注册中心组件&#xff1a;Eureka、Nacos 负载均衡组件&#xff1a;Ribbon 远程调用组件&#xff1a;OpenFeign 网关组件&#xff1a;Zuul、Gateway 服务保护组件&#xff1a;Hystrix、Sentinel 服务配置管理组件&#xff1a;SpringCloudConfig、Nac…

Qt跨平台开发demo(适用萌新)

最近需要参与一款Qt跨平台的软件开发&#xff0c;在此之前&#xff0c;特把基础信息做学习和梳理&#xff0c;仅供参考。 所使用的技术和版本情况如下&#xff1a; 虚拟机&#xff1a;VMware 16.2.5操作系统&#xff1a;ubuntu-20.04.6-desktop-amd64&#xff1a;Mysql数据库…

纯血鸿蒙APP实战开发——数字滚动动效实现

介绍 本示例主要介绍了数字滚动动效的实现方案。 该方案多用于数字刷新&#xff0c;例如页面刷新抢票数量等场景。 效果图预览 使用说明&#xff1a; 下拉页面刷新&#xff0c;数字进行刷新。 实现思路 通过双重ForEach循环分别横向、纵向渲染数字。 Row() {ForEach(this…

服装店会员管理系统结合小程序商城帮你挖掘出潜在客户

在现代社会&#xff0c;随着科技的不断进步和人们消费习惯的变化&#xff0c;传统的服装店已经不再能够满足消费者的需求。为了更好地服务客户&#xff0c;提升销售业绩&#xff0c;许多服装店开始引入会员管理系统&#xff0c;并结合小程序商城&#xff0c;实现线上线下的无缝…

AJAX前端与后端交互技术知识点以及案例

Promise promise对象用于表示一个异步操作的最终完成&#xff08;或失败&#xff09;及其结果值 好处&#xff1a; 逻辑更清晰了解axios函数内部运作机制成功和失败状态&#xff0c;可以关联对应处理程序能解决回调函数地狱问题 /*** 目标&#xff1a;使用Promise管理异步任…

C++ int 学习

在C语言中 & 是取地址符号&#xff1b; 在C中有 int& 这样的&#xff0c;这里的&不是取地址符号&#xff0c;而是引用符号&#xff1b; 引用是C对C的一个补充&#xff1b; 变量的引用就是变量的别名&#xff0c;讲的通俗一点就是另外一个名字&#xff1b; a的值…

鸿蒙开发接口Ability框架:【(AbilityContext)】

AbilityContext AbilityContext是Ability的上下文环境&#xff0c;继承自Context。 AbilityContext模块提供允许访问特定于ability的资源的能力&#xff0c;包括对Ability的启动、停止的设置、获取caller通信接口、拉起弹窗请求用户授权等。 说明&#xff1a; 本模块首批接口…

040——移植数据库sqlite3到i.mx6ull

目录 一、下载 二、移植数据库 三、测试sqlite3 一、下载 SQLite Download Page 暂时先下载最新版的试试&#xff0c;我们以前其实在ubuntu上直接使用过 嵌入式数据库sqlite3_常见的嵌入式数据库-CSDN博客 当时我把常用的操作和怎么使用记录下来了 现在把他移植到开发板…

Android11 InputManagerService启动流程分析

InputManagerService在systemserver进程中被启动 //frameworks\base\services\java\com\android\server\SystemServer.java t.traceBegin("StartInputManagerService"); inputManager new InputManagerService(context);//1 t.traceEnd(); //省略 //注册服务 Servi…

C++11:常用语法汇总

目录 &#x1f341;统一的列表初始化 { }initializer_list &#x1f341;decltype 推导表达式类型&#x1f341;可变参数模板解析可变参数包方法一方法二 &#x1f341;lambda 表达式捕捉列表的使用运用场景举例lambda表达式 与 函数对象 &#x1f341;统一的列表初始化 { } 在…

2024最新商业视频打赏系统源码 多套模板 有代理后台 已对接支付

简介&#xff1a; 2024最新商业视频打赏系统源码 多套模板 有代理后台 已对接支付 图片&#xff1a; 源码下载