Linux多线程基本概念

目录

​编辑

1.什么是进程,线程,并发,并行

优点

缺点

 什么资源是线程应该私有的呢

为什么线程切换成本更低呢

3.线程控制

pthread_create

 lpthread选项

 makefile

代码实现

 ps -aL

 什么是LWP

 轻量级进程ID与进程ID之间的区别

LWP与pthread_create创建的线程之间的关系

4.线程中止,等待,分离

pthread_exit函数

pthread_cancel函数 

 线程等待

pthread_join​编辑

 线程分离 

pthread_detach

5.线程互斥

进程线程间的互斥相关背景概念

 多线程共享资源访问的不安全问题(举个抢票的例子)

互斥量的接口


今天推荐一首歌曲 

黄昏      周传雄

依然记得从你口中~

说出再见坚决如铁~

昏暗中有种烈日灼身的错觉~

黄昏的地平线~

划出一句离别~

爱情进入永夜~

开始我们的学习吧!

1.什么是进程,线程,并发,并行

进程是资源分配的最小单位,有独立的地址空间和系统资源。
线程是cpu调度,程序执行的最小单位,一个线程只属于一个进程,而一个进程可以有多个线程,多个线程共享同一个进程的资源。在多核系统下允许几个线程各自独立的在处理器上运行,操作系统提供线程就是为了方便有效地实现这种并发性。

一切进程至少都有一个执行线程

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

在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化

多进程:同时运行QQ、微信、浏览器 , 多线程: 用浏览器同时进行浏览网页、播放视频)

并发是把cpu运行时间划分成若干个时间段,每个时间段再分配给各个线程执行, 当一个线程在运行时,其他线程处于挂起状态。

并行是同一时刻当一个cpu执行一个线程时,另一个cpu可以执行另一个线程,两个线程互不抢占cpu资源,是真正意义上的 不同线程在同一时刻同时执行

形象地解释:
进程是拥有一系列资源的集合,这些资源包括内存空间、内核对象、资源文件等等。我们将进程理解为一个工厂,工厂本身不能运作,需要有人来操作。那么这些工人就是线程,每一个工人操作自己的一台设备,这个设备就可以看成是线程的栈,他由这个工人自己使用。一个工厂里有多台设备时,如果只有一个人那么他就需要去一个个的去操作工厂里的设备,如果这些设备需要同时运行,那么这样操作效率太低。因此,工厂会多聘用几个工人,他们每个人操作自己的设备,这样效率就会大大提高。工人在操作设备时,可能两个人需要使用同一个工具,这个工具是全局的变量,因此他们可以共同访问,但是一个工人要去使用这个工具时,他会等在那里,等另一个人使用完,然后他就可以接过工具,继续干活了,这就是线程的同步。创建多个工厂就是多进程程序。工人操作的每台设备还是属于该工厂,因此线程是依附于进程的,占用进程的地址空间,线程之间也可以相互访问对方的地址,需要通过传址能实现,但是一般不会出现这样的情况,试想能有多大的机会在一个函数中访问另一个函数的的局部变量。在代码的实现中,我们可以将线程仅仅看成一函数去分析,只不过他是并发进行的。


工厂就是进程,工人就是线程,工厂所占的位置就是进程空间,工厂里的设备和工具就是数据和资源,多个工人同时工作就是多线程,几个工人要用同时使用一个工具就是线程同步。

重点

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


线程独有:栈,寄存器,信号屏蔽字,errno...等信息,因此各个线程各自有各自的栈区,但是堆区共用

任何一个线程都可以创建或撤销另一个线程

进程比线程安全的原因是每个进程有独立的虚拟地址空间

Linux内核中有没有真正意义上的线程呢?没有,linux用进程的PCB来模拟线程,是完全属于自己实现的一套方案!
站在CPU的角度来看,每一个PCB,都可以称之为轻量级进程,因为它只需要PCB即可,而进程承担分配的资源更多,量级更重!
Linux线程是CPU调度的基本单位,进程是承担分配系统资源的基本实体!

2.线程的优缺点

优点


缺点

 什么资源是线程应该私有的呢

重要的两点

线程的上下文结构也必须是线程的私有资源。(寄存器)
每个线程都有自己的私有栈结构

 

为什么线程切换成本更低呢

3.线程控制

在Linux下,PCB<=其他OS内的PCB(进程控制块) 

Linux下的进程统称为,轻量级进程

pthread_create

pthread_create是创建线程的一个接口

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

 lpthread选项

(如果在编译时不带-lpthread选项,可以看到g++报错pthread_create()函数未定义,其实就是因为链接器链接不上具体的动态库,此时就可以看出来linux内核中并没有真正意义的线程,他无法提供创建线程的接口,而只能通过第三方库libpthread.so或libpthread.a来提供创建线程的接口。)

 makefile
//编写makefile  带上 -lpthread
test:test.cppg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f test
代码实现
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void *threadRoutine(void *args)//新线程回调在这里
{
while(true)
{
cout<<"新线程:"<<(char*)args <<" running..."<< endl;
sleep(1);
}}int main()
{pthread_t tid;  //创建线程pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");//进程的名字//下面是主线程
while(true)
{
cout<<"main线程:"<<" running..."<< endl;
sleep(1);
}
}

 ps -aL

ps命令用于查看进程信息,其中-L选项用于查看轻量级进程信息
pthread_self() 用于获取用户态线程的tid,而并非轻量级进程ID
getpid() 用于获取当前进程的id,而并非某个特定轻量级进程

通过ps -aL就可以看到正在运行的线程有哪些,可以看到有两个标识符,一个是PID,一个是LWP(light weight process),所以CPU在调度那么多的PCB时,其实是以LWP作为每个PCB的标识符,以此来区分进程中的多个轻量级进程。


主线程的PID和LWP是相同的,所以从CPU调度的角度来看,如果进程内只有一个执行流,那么LWP和PID标识符对于CPU来说都是等价的,但当进程内有多个执行流时,CPU是以LWP作为标识符来调度线程,而不是以PID来进行调度。

 什么是LWP

LWP(轻量级进程)是操作系统中用于调度和管理线程的内核层面的实体。

LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化

 轻量级进程ID与进程ID之间的区别

  1. 概念:

    • 进程ID(PID)是操作系统为每个正在运行的进程分配的唯一标识符。它是在进程创建时由操作系统分配的,并在整个进程的生命周期中保持不变。
    • 轻量级进程ID(LWP ID)是在多线程操作系统中,用于标识线程或轻量级进程(也称为执行上下文)的标识符。一个进程可以包含多个轻量级进程,并且每个轻量级进程都有自己的LWP ID。
  2. 操作方式:

    • 对于PID,通常可以使用系统调用(如fork()和exec())创建新进程或操作现有进程。
    • 对于LWP ID,通常使用线程库(如pthread)创建新线程或操作现有线程。一个进程的LWP ID只在该进程内部可见,对于其他进程来说是不可见的。
  3. 调度与资源分配:

    • 操作系统通过PID进行进程调度和资源分配。进程调度通常是基于运行队列中进程的优先级和调度算法来进行决策的。
    • 在多线程操作系统中,内核会将CPU时间划分给不同的LWP。调度和资源分配是基于LWP而不是整个进程进行的。
  4. 上下文切换:

    • 在进程切换时,操作系统需要保存和恢复整个进程的上下文,这包括进程的寄存器状态、打开的文件、堆栈等。
    • 在轻量级进程切换时,只需要保存和恢复当前线程的上下文,这是因为同一进程内的线程共享同一内存空间和打开的文件。

需要注意的是,LWP ID是在多线程操作系统中使用的概念,而PID是在所有操作系统中都存在的概念。在某些操作系统中,LWP ID可能与线程ID(TID)或任务ID(TID)等概念等效或相似。

LWP与pthread_create创建的线程之间的关系

LWP(轻量级进程)与pthread_create创建的线程之间存在一种关系,可以理解为LWP是内核层面对线程的调度和管理的实体,而pthread_create创建的线程则是用户层面对线程的抽象。

具体来说,pthread_create是一个线程库函数,用于在用户空间创建一个新的线程。这个线程由操作系统内核分配一个LWP,并将其标识为一个用户线程,也称为轻量级进程。该LWP会在它所属的进程中与其他LWP共享进程的地址空间、文件描述符等资源。

LWP与pthread_create创建的线程之间的关系可以总结如下:

  1. 一个进程可以包含多个LWP,每个LWP都有一个唯一的LWP ID。
  2. 每个LWP可以与一个或多个pthread_create创建的线程相关联,这些线程共享其所属进程的资源。
  3. 每个pthread_create创建的线程有自己的线程ID,并且在用户层面上可见,可以使用线程库提供的函数进行操作和管理。

需要注意的是,LWP的创建和管理是由操作系统内核完成的,而pthread_create函数是线程库提供的接口,它在内部会使用操作系统提供的系统调用来创建和管理LWP。因此,对于用户来说,他们只需要使用pthread_create接口来创建和操作线程,而无需直接与LWP进行交互。


4.线程中止,等待,分离

线程终止总共有三种方式,分别为return,pthread_exit,pthread_cancel

1. 从线程函数 return 。这种方法对主线程不适用 , main 函数 return 相当于调用 exit
(exit是中止进程的)
2. 线程可以调用 pthread_ exit 终止自己。
3. 一个线程可以调用 pthread_ cance l 终止同一进程中的另一个线程。
pthread_exit函数

那个线程调用pthread_exit函数, 那个线程就退出。俗称“谁调用谁退出” 

pthread_cancel函数 

在有多个线程的情况下,主线程调用pthread_cancel(pthread_self()), 则主线程状态为Z, 其他线程正常运行

主线程调用pthread_exit只是退出主线程,并不会导致进程的退出 

 线程等待

与进程类似,进程退出之后要被等待,也就是回收进程的资源,否则会出现僵尸进程,僵尸的这种状态可以通过ps指令+axj选项看到,同时会产生内存泄露的问题。


线程终止同样也需要被等待,但线程这里没有僵尸线程这样的概念,如果不等待线程同样也会造成资源泄露,也就是PCB资源未被回收,线程退出的状态我们是无法看到的,我们只能看到进程的Z状态。

pthread_join

原生线程库给我们提供了对应的等待线程的接口,其中join的第二个参数是一个输出型参数,在join的内部会拿到线程函数的返回值,然后将返回值的内容写到这个输出型参数指向的变量里面,也就是写到我们用户定义的ret指针变量里,通过这样的方式来拿到线程函数的返回值。

 线程分离 

若要进行分离,推荐创建完线程之后立马设置分离

pthread_detach

新创建出来的线程默认状态是joinable的,也就是说你必须通过pthread_join去等待线程,否则就会造成内存泄露。


但如果我们压根就不想等待线程,那调用pthread_join就是一种负担,这个时候我们就可以通过分离线程的手段,来告诉操作系统,现在我这个线程要和进程分离了,我不再共享进程的地址空间了,我也不要进程的任何资源了,我们俩人以后就形同陌路,互不相干了!操作系统你现在就把我回收吧,我已经和进程没有任何关系了!


所以在设置线程为分离状态后,操作系统会立即回收线程的所有资源,而不需要等待线程自动退出或者是手动来释放资源,表示我们现在已经不关心这个线程了!


joinable和detach是线程的两个对立的状态,一个线程不能既是joinable又是分离的,并且如果线程被设置为detach,那么就不可以用join来等待线程,否则是会报错的

int pthread_detach(pthread_t thread);

 

5.线程互斥
 

进程线程间的互斥相关背景概念

临界资源: 多线程执行流共享的资源就叫做临界资源
临界区: 每个线程内部,访问临界资源的代码,就叫做临界区
互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

 多线程共享资源访问的不安全问题(举个抢票的例子)

假设现在有一份共享资源tickets,如果我们想让多个线程都对这个资源进行操作,也就是tickets- -的操作,但下面两份代码分别出现了不同的结果,上面代码并没有出现问题,而下面代码却出现了票为负数的情况,这是怎么回事呢?

其实问题产生就是由于多线程被调度器调度的特性导致的。

抢票代码 

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<cstdio>
#include<iostream>using namespace std;//加锁保护
//pthread_mutex_t mtx =PTHREAD_MUTEX_INITIALIZER; 
//pthread_mutex_t  是原生线程库提供的一个数据类型int tickets=3000;
//在并发访问的时候,会导致数据不一致的问题void *gettickets(void *args)
{(void*)args;   //每个线程内部,访问临界资源的代码,就叫做临界区while(true){//pthread_mutex_lock(&mtx);  //加锁if(tickets>0){usleep(10000);printf("%s:%d\n",(char*)args,tickets);tickets--;// pthread_mutex_unlock(&mtx); //解锁}else{// pthread_mutex_unlock(&mtx); break;}}return nullptr;
}int main()
{pthread_t t1,t2,t3;//线程创建pthread_create(&t1,nullptr,gettickets,(void*)"thread one");pthread_create(&t2,nullptr,gettickets,(void*)"thread two");pthread_create(&t3,nullptr,gettickets,(void*)"thread three");//线程等待pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);return 0;
}

结果会出现负数(多线程在并发访问的时候,可能会导致数据不一致的问题)

要解决以上问题,需要做到三点:
代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量

谁持有锁谁才能进入临界区,你没有锁那就只能在临界区外面乖乖的阻塞等待,等待锁被释放,然后你去竞争这把锁,竞争到就拿着锁进入临界区执行代码,竞争不到就老样子,继续乖乖的在临界区外面阻塞等待

互斥量的接口
初始化互斥量有两种方法:
静态分配:
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

 动态分配: 

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t
*restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL

把上面的代码改成这样,就不会出现出现负数的问题,这里用的是静态分配 

//加锁保护
pthread_mutex_t mtx =PTHREAD_MUTEX_INITIALIZER; //pthread_mutex_t 是原生线程库提供的一个数据类型 ,静态int tickets=3000;
//在并发访问的时候,会导致数据不一致的问题void *gettickets(void *args)
{(void*)args;   //每个线程内部,访问临界资源的代码,就叫做临界区while(true){pthread_mutex_lock(&mtx);  //加锁if(tickets>0){usleep(10000);printf("%s:%d\n",(char*)args,tickets);tickets--;pthread_mutex_unlock(&mtx); //解锁}else{pthread_mutex_unlock(&mtx); break;}}return nullptr;
}

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

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

相关文章

软件测试行情堪忧,测试行业将迎来低谷?

前两天跟一个HR朋友聊天&#xff0c;她表示刚在boss上发布了一个普通测试岗位&#xff0c;不到一小时竟然收到了几百份简历。而且简历质量极高&#xff0c;这是往年不敢想象的。岗位少&#xff0c;竞争激烈&#xff0c;这是今年软件测试就业的真实写照&#xff0c;也是所有岗位…

SWT技巧

实现控件的刷新 问题可以简化如下&#xff0c;点击上方按钮&#xff0c;使下方按钮移动&#xff0c;但要求在监听事件里新建按钮对象&#xff0c;而不是使用原来的按钮&#xff08;原来的按钮被移除了&#xff09;。 解决代码如下&#xff1a; public class TestUI {protecte…

【高效开发工具系列】PlantUML入门使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

SpringBoot : ch07 整合websocket

前言 当涉及到在Spring Boot应用程序中整合WebSocket时&#xff0c;我们可以使用Spring框架提供的功能来实现实时双向通信。WebSocket是一种在Web浏览器和服务器之间进行全双工通信的协议&#xff0c;它允许服务器主动向客户端发送消息&#xff0c;而不需要客户端发起请求。 …

387. 字符串中的第一个唯一字符

387. 字符串中的第一个唯一字符 描述 : 给定一个字符串 s &#xff0c;找到 它的第一个不重复的字符&#xff0c;并返回它的索引 。如果不存在&#xff0c;则返回 -1 。 题目 : 387. 字符串中的第一个唯一字符 分析 : 我们可以对字符串进行两次遍历&#xff0c;在第一次遍…

Redis原理之五种数据类型笔记

目录 String List Set ZSet ​ Hash String List Set ZSet Hash

Shell脚本:Linux Shell脚本学习指南(第二部分Shell编程)四

第二部分&#xff1a;Shell编程&#xff08;四&#xff09; 三十一、Shell test命令&#xff08;Shell []&#xff09;详解&#xff0c;附带所有选项及说明 test 是 Shell 内置命令&#xff0c;用来检测某个条件是否成立。test 通常和 if 语句一起使用&#xff0c;并且大部分…

RHEL开发者授权注册

$ sudo subscription-manager register --usernameusername --passwordpassword$ sudo subscription-manager attach --auto查看是否注册 Red Hat 订阅管理&#xff0c;请运行以下命令&#xff1a; $ sudo subscription-manager list --installed

【数据库】执行计划中的两趟算法机制原理,基于排序算法来分析,算法的限制,执行代价以及优化

基于排序的两趟算法 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会定…

Java计算二叉树从根节点到叶子结点的最大路径和

要求从根节点到叶子结点的最大路径和&#xff0c;可以通过递归遍历二叉树来实现。对于二叉树中的每个节点&#xff0c;我们都可以考虑包含该节点的最大路径和。在递归的过程中&#xff0c;我们需要不断更新全局最大路径和。 具体的思路如下&#xff1a; 递归函数设计&#xff1…

服务化通信OPC实操

实操也是基于视频进行一些笔记&#xff0c;没得写就少写了 准备 Nuget包准备&#xff1a;OPCfoundation 一般都是使用Ua&#xff0c;当然也是有&#xff1a; 客户端链接服务器参数&#xff1a;IP Port 认证 登录用户名 Session 的实例化创建 进行使用&#xff1a; 因为Ses…

数据中台具体是怎么解决数据孤岛的?_光点科技

在数字化时代&#xff0c;数据已成为企业的核心资产。然而&#xff0c;由于历史遗留问题、部门壁垒等因素&#xff0c;很多企业面临着“数据孤岛”的问题。数据孤岛是指在一个组织内&#xff0c;数据被分散在不同的系统中&#xff0c;彼此隔离&#xff0c;不能有效整合和利用。…

【版本管理 | Git 】Git最佳实践系列(一) —— LFS .gitignore 最佳实践,确定不来看看?

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

python subprocess

查看python官方文档&#xff1a;最全 p subprocess.Popen([rpng2bdf.exe,[r-o .\tst\myfont.bdf -f myfont -e 65 tst\*.png]],stdoutsubprocess.PIPE,stderr subprocess.PIPE) out,err p.communicate() print(out) 注意&#xff0c;如何将shell命令分解为参数序列可能并…

【文末送书】程序员如何化解35岁危机?

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

报表生成器Stimulsoft用户手册:深入报告

Stimulsoft Reports 是一款报告编写器&#xff0c;主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署&#xff0c;如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等&#xff0c;在你的应用程序中嵌入报告设计器…

canvas基础:绘制线段,绘制多边形

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 使用…

【Linux】第二十一站:文件(一)

文章目录 一、共识原理二、C系列文件接口三、从C过渡到系统&#xff1a;文件系统调用四、访问文件的本质 一、共识原理 文件 内容 属性 文件分为打开的文件 和 没打开的文件 打开的文件&#xff1a;是谁打开的&#xff1f;是进程&#xff01;----所以研究打开的文件本质是研…

使用群晖Synology Office提升生产力:如何多人同时编辑一个文件

使用群晖Synology Office提升生产力&#xff1a;多人同时编辑一个文件 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 文章目录 使用群晖Synol…

【腾讯云云上实验室】探索向量数据库背后的安全监控机制

当今数字化时代&#xff0c;数据安全成为了企业和个人最为关注的重要议题之一。随着数据规模的不断增长和数据应用的广泛普及&#xff0c;如何保护数据的安全性和隐私性成为了迫切的需求。 今天&#xff0c;我将带领大家一起探索腾讯云云上实验室所推出的向量数据库&#xff0c…