Linux:41线程控制lesson29

1.线程的优点:

• 创建⼀个新线程的代价要⽐创建⼀个新进程⼩得多

创建好线程只要调度就好了

• 与进程之间的切换相⽐,线程之间的切换需要操作系统做的⼯作要少很多

为什么?

◦ 最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上 下⽂切换的处理都是通过操作系统内核来完成的。

内核的这种切换过程伴随的最显著的性能 损耗是将寄存器中的内容切换出。

◦ 另外⼀个隐藏的损耗是上下⽂的切换会扰乱处理器的缓存机制。简单的说,⼀旦去切换上下 ⽂,处理器中所有已经缓存的内存地址⼀瞬间都作废了。还有⼀个显著的区别是当你改变虚 拟内存空间的时候,处理的⻚表缓冲 TLB (快表)会被全部刷新,这将导致内存的访问在⼀ 段时间内相当的低效。但是在线程的切换中,不会出现这个问题,当然还有硬件cache。

• 线程占⽤的资源要⽐进程少

• 能充分利⽤多处理器的可并⾏数量

线程是调度的基本单位

在等待慢速I/O操作结束的同时,程序可执⾏其他的计算任务

多进程也可以做,也是进程的优点

• 计算密集型应⽤,为了能在多处理器系统上运⾏,将计算分解到多个线程中实

加密,解密,压缩。

通俗解释:计算机多个CPU,把计算任务拆分成多份,分别跑

最好只创建跟CPU对等的线程

• I/O密集型应⽤,为了提⾼性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程可以多创建。概率上总会有线程读数据,执行。

线程切换 

(1)进程切换:PCB切成另一个进程的PCB。

task_struct*current:表示OS全局指针,current指向当前进程,寄存器。换跟换。

(2)线程切换:?
成本比进程低为什么?

同一个进程的线程切换:
不用保存CR3寄存器。------>看不出来谁的成本更低
页表不用切换。
 

为什么进程切换成本高???

进程切换的两个大缓存:

(1)对用户数据进行cache:
cache缓存:
Cache(缓存)是计算机系统中的一种高速存储器,位于CPU和主内存之间,用于暂时存储CPU可能频繁访问的数据和指令。Cache的主要作用是减少CPU访问主内存的次数,从而提高系统的整体性能。
Cache的访问速度非常快,通常在几个时钟周期内就可以完成数据的读取或写入。通过将CPU频繁访问的数据和指令存储在Cache中,CPU可以直接从Cache中读取数据,从而大大减少了访问主内存的次数,提高了系统性能

(2)TLB快表

总结:

进化切换,会导致TLB和Cache失效,下次运行,需要重新缓存

线程不切换页表,不会出现缓存失效,所以线程切换成本更低。

2.线程的缺点 

• 性能损失

◦ ⼀个很少被外部事件阻塞的计算密集型线程往往⽆法与其它线程共享同⼀个处理器。如果计 算密集型线程的数量⽐可⽤的处理器多,那么可能会有较⼤的性能损失,这⾥的性能损失指 的是增加了额外的同步和调度开销,⽽可⽤的资源不变。

• 健壮性降低

◦ 编写多线程需要更全⾯更深⼊的考虑,在⼀个多线程程序⾥,因时间分配上的细微偏差或者 因共享了不该共享的变量⽽造成不良影响的可能性是很⼤的,换句话说线程之间是缺乏保护 的。

• 缺乏访问控制

◦ 进程是访问控制的基本粒度,在⼀个线程中调⽤某些OS函数会对整个进程造成影响。

缺乏访问控制,也是优点,可以自由地访问资源

• 编程难度提⾼

◦ 编写与调试⼀个多线程程序⽐单线程程序困难得多

3.线程异常

• 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃

• 线程是进程的执⾏分⽀,线程出异常,就类似进程出异常,进⽽触发信号机制,终⽌进程,进程 终⽌,该进程内的所有线程也就随即退出

Linux进程VS线程

进程和线程

• 进程是资源分配的基本单位

• 线程是调度的基本单位

• 线程共享进程数据,但也拥有⾃⼰的⼀部分“私有”数据::

◦ 线程ID

◦ ⼀组寄存器,线程的上下文

“线程要有自己的独立上下文数据 - >线程是可以被独立调度的。”

◦ 栈

线程都有自己独立的栈结构->线程是一个动态的概念(“有生命周期”)。

◦ errno “错误码”

◦ 信号屏蔽字

◦ 调度优先级

进程大部分资源独占,少量资源共享
线程相反。

进程被多个线程共享

同⼀地址空间,因此Text Segment、Data Segment都是共享的,如果定义⼀个函数,在各线程中都可以调 ⽤,如果定义⼀个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

• ⽂件描述符表

• 每种信号的处理⽅式(SIG_IGN、SIG_DFL或者⾃定义的信号处理函数)

• 当前⼯作⽬录

• ⽤⼾id和组id

进程和线程的关系如下图:

关于进程线程的问题

• 如何看待之前学习的单进程?具有⼀个线程执⾏流的进程

 Linux线程控制

        创建一个线程

功能:创建⼀个新的线程 原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

(1)参数: thread:返回线程ID attr:设置线程的属性,attr为NULL表⽰使⽤默认属性

(2)start_routine:是个函数地址,线程启动后要执⾏的函数

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

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

代码 

Makefile
test_thread:TestThread.ccg++ -o $@ $^ 
.PHONY:clean
clean:rm -f test_thread
 TestThread.cc:自己写的√
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>using namespace std;void* threadstat(void*args){string name = (const char*)args;while(true){cout<<"我是子进程,name:"<<name<<endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadstat,(void*)"thread-1");while(true){cout<<"我是主线程"<<endl;sleep(10);}
}

 编译不通过

不是系统调用

test_thread:TestThread.ccg++ -o $@ $^ -lpthread
.PHONY:clean
clean:rm -f test_thread

需要加上库pthread,-l:引入库文件的操作

编译通过:结果,没有子进程,两个死循环跑起来

 只有一个进程,杀掉,两个都没了。

理解 

ps -aL:查看线程

创建线程成功:
新线程执行:threadrun,新线程入口
编译出来就是一组虚拟地址

main函数,就是另一组虚拟地址,表示代码和数据。

pid:是一样的,属于同一个进程
TTY:终端一样
LWP-light weight process:轻量级进程
“主进程,pid和lwp一样”
“线程pid和主进程一样,lwp与pid不一样”

task_strut里面有两个数据

pid_t pid;
pid_t lwp;

问题 

1.关于调度的时间片问题:时间片是等分给不同的线程。
2.线程异常??

代码:√
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>using namespace std;void* threadstat(void*args){string name = (const char*)args;while(true){cout<<"我是子进程,name:"<<name<<endl;sleep(1);int a = 1;a/=0;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadstat,(void*)"thread-1");while(true){cout<<"我是主线程"<<endl;sleep(10);}
}

任何一个线程崩溃,都会导致整个进程崩溃

3.消息混在一起?
显示器是共享资源

引入pthread库 

为什么会有一个库?这个库是什么东西?

c++11多线程demo

c++11也引入了多线程叫做
thread


 

#include <iostream>
#include <string> 
#include <thread>void hello(){while (true){std::cout << "新线程: hello world, pid: " << getpid() << std::endl;sleep(1);}}int main(){std::thread t(hello);while (true){std::cout << "我是主线程..." << ", pid: " << getpid() << std::endl;sleep(1);}t.join();return 0;}

报错,由于没有使用原生线程库pthread. 

test_thread:TestThread.ccg++ -o $@ $^ -std=c++11 -
.PHONY:clean
clean:rm -f test_thread

这样就OK

test_thread:TestThread.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f test_thread

 c++11的多线程,在linux下,本质就是堆pthread的封装

 

c++,在不同的环境下,对线程进程不同的封装。 

所有语言的线程都要独立进行封装

 线程控制的接口

创建线程:pthread_create

功能:创建⼀个新的线程 原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

(1)参数: thread:返回线程ID attr:设置线程的属性,attr为NULL表⽰使⽤默认属性

(2)start_routine:是个函数地址,线程启动后要执⾏的函数

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

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

 终止线程:pthread_exit | pthred_cancle

如果需要只终⽌某个线程⽽不终⽌整个进程,可以有三种⽅法:

pthread_exit函数:
功能:线程终⽌
原型:void pthread_exit(void *value_ptr);参数:value_ptr:value_ptr不要指向⼀个局部变量。
返回值:⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是⽤malloc分配的, 不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。  

pthread_cancel函数
功能:取消⼀个执⾏中的线程 原型: int pthread_cancel(pthread_t thread);参数: thread:线程ID返回值:成功返回0;失败返回错误码
线程等待:pthread_join

为什么需要线程等待?

• 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。

• 创建新的线程不会复⽤刚才退出线程的地址空间。

 线程创建好之后,新线程要被主线程等待

---->不然会触发,类似僵尸进程,内存泄漏

功能:等待线程结束
原型int pthread_join(pthread_t thread, void **value_ptr);参数:thread:线程IDvalue_ptr:它指向⼀个指针,后者指向线程的返回值返回值:成功返回0;失败返回错误码

调⽤该函数的线程将挂起等待,直到id为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线程的终⽌状态不感兴趣,可以传NULL给value_ptr参数。

#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstdio>using namespace std;int flag = 100;
void showid(pthread_t &tid)//减少拷贝
{printf("tid: 0x%lx",tid);
}string Formatid(const pthread_t &tid){char id[64];snprintf(id,sizeof(id),"0x%lx",tid);return id;
}void* threadstat(void*args){string name = (const char*)args;pthread_t id = pthread_self();int cnt = 5;while(cnt){cout<<"我是子进程,name:"<<name<<"我的Id是"<<Formatid(id)<<endl;;sleep(1);cnt--;flag++;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadstat,(void*)"thread-1");showid(tid);int cnt = 6;while(cnt--){cout<<"我是主线程"<<"我的Id是"<<Formatid(pthread_self())<<"flag:"<<flag<<endl;sleep(1);}void *ret = nullptr; // ret也是一个变量!!也有空间哦!// 等待的目标线程,如果异常了,整个进程都退出了,包括main线程,所以,join异常,没有意义,看也看不到!// jion都是基于:线程健康跑完的情况,不需要处理异常信号,异常信号,是进程要处理的话题!!!pthread_join(tid, &ret); // 为什么在join的时候,没有见到异常相关的字段呢??std::cout << "ret is : " << (long long int)ret << std::endl;return 0;
}

返回值存放在ret里面,可以查看,长整型。

返回线程的tid:pthread_self

谁调用,就获取谁的tid

std::string FormatId(const pthread_t &tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}

 线程传参和返回值

主线程在调用FormatId,
新线程也会调用。
该函数被称为可重入函数 

 代码1:验证join可以去的线程执行完后的退出码/返回值

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
using namespace std;void* routine(void* arg){string name = static_cast<const char*>(arg);int cnt = 5;while(cnt--){cout<<"我是子线程,名字:"<<name<<endl;sleep(1);}return (void*)10;
}int main(){pthread_t tid;pthread_create(&tid,nullptr,routine,(void*)"thread-1");int cnt = 5;while(cnt--){cout<<"我是主线程"<<endl;sleep(1);}void* ret = nullptr;pthread_join(tid,&ret);cout<<"子进程退出,退出码为:"<<(long long)ret<<endl;
}

1.main函数结束,代表主线程结束,也代表进程结束
2.新线程对应的入口函数,运行结束,代表当前线程运行结束。
3.问题:给线程传递的参数和返回值,可以是任意类型 

代码2:30min证明::给线程传递的参数和返回值,可以是任意类型 ,下面的例子是类类型

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
using namespace std;class Task{public:Task(int a,int b):_a(a),_b(b){}int Cal(){return _a+_b;}~Task() {}private:int _a;int _b;
};
class Result{public:Result(int result):_result(result){}int getresult(){return _result;}~Result(){}private:int _result;
};
void* routine(void* arg){Task*t = static_cast<Task*>(arg);sleep(1);Result* r = new Result(t->Cal());sleep(1);return r;
}int main(){pthread_t tid;Task* t = new Task(20,10);pthread_create(&tid,nullptr,routine,t);Result* ret = nullptr;pthread_join(tid,(void**)&ret);int n = ret->getresult();cout<<"子线程的返回值是:"<<n<<endl;
}

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

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

相关文章

【MCP】从一个天气查询服务带你了解MCP

1. 前言 这篇文章将通过一个集成高德天气查询的 MCP Server 用例&#xff0c;带你上手开发自己的 MCP Server ,文章将通过以下三种方式&#xff08;自己编写 Client 端代码&#xff0c;使用 mcp-cli 自带页面&#xff0c;集成到 Claude 桌面版等&#xff09;带你测试自己的 MC…

SHCTF-REVERSE

前言 之前写的&#xff0c;一直没发&#xff0c;留个记录吧&#xff0c;万一哪天记录掉了起码在csdn有个念想 1.ezapk 反编译 快速定位关键函数 package com.mycheck.ezjv;import adrt.ADRTLogCatReader; import android.app.Activity; import android.content.Context; impo…

安卓触摸事件分发机制分析

1. 前言 &#x1f3af; 一句话总结&#xff1a; 触摸事件&#xff08;TouchEvent&#xff09;会从 Activity 层开始&#xff0c;按从外到内的方式传递给每一个 ViewGroup/View&#xff0c;直到某个 View 消费&#xff08;consume&#xff09; 它&#xff0c;事件传递就会停止…

Spring MVC 多个拦截器的执行顺序

一、流程总览 该流程图描述了一个多层拦截器链的业务处理流程&#xff0c;核心逻辑为&#xff1a; 前置拦截&#xff1a;通过 predHandler1 和 predHandler2 逐层校验请求合法性。核心处理&#xff1a;通过校验后执行核心业务逻辑 handler()。后置处理与清理&#xff1a;按反…

django filter 排除字段

在Django中&#xff0c;当你使用filter查询集&#xff08;QuerySet&#xff09;时&#xff0c;通常你会根据模型的字段来过滤数据。但是&#xff0c;有时你可能想要排除某些特定的字段&#xff0c;而不是过滤这些字段。这里有几种方法可以实现这一点&#xff1a; 使用exclude方…

ByeCode,AI无代码开发平台,拖拽式操作构建应用

ByeCode是什么 ByeCode 是一款先进的 AI 无代码平台&#xff0c;旨在帮助企业迅速创建数字名片、网站、小程序、应用程序及内部管理系统&#xff0c;无需繁杂的编码或开发工作。ByeCode 采用直观的可视化界面和拖拽式操作&#xff0c;使得非技术用户能够轻松上手。同时&#x…

AI日报 - 2025年04月28日

&#x1f31f; 今日概览(60秒速览) ▎&#x1f916; 能力进展 | Gemini 2.5 Pro成功挑战《口袋妖怪红》8道馆&#xff1b;AI推理器具备自我纠错能力&#xff1b;LLM在游戏、多模态理解、代码迁移等方面展现新能力。 ▎&#x1f4bc; 商业动向 | Google回应DOJ反垄断案&#xff…

在Java中实现List按自定义顺序排序的几种方案

在Java中实现List按自定义顺序排序的几种方案 在实际开发中&#xff0c;我们经常需要对集合中的对象按照特定字段进行排序。当排序规则不是简单的字母或数字顺序&#xff0c;而是自定义的顺序时&#xff0c;我们需要采用特殊的方法。本文将以一个List<Person>按省份特定…

微服务架构在云原生后端的深度融合与实践路径

📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:后端架构的演变,走向云原生与微服务融合 过去十余年,后端架构经历了从单体应用(Monolithic)、垂直切分(Modularization)、到微服务(Microservices)的演进,每一次变化都是为了解决…

Python中的Walrus运算符分析

Python中的Walrus运算符&#xff08;:&#xff09;是Python 3.8引入的一个新特性&#xff0c;允许在表达式中同时赋值和返回值。它的核心作用是减少重复计算&#xff0c;提升代码简洁性。以下是其适用的典型场景及示例&#xff1a; 1. 在循环中避免重复计算 当循环条件需要多次…

用Node.js施展文档比对魔法:轻松实现Word文档差异比较小工具,实现Word差异高亮标注(附完整实战代码)

引言&#xff1a;当「找不同」遇上程序员的智慧 你是否经历过这样的场景&#xff1f; 法务同事发来合同第8版修改版&#xff0c;却说不清改了哪里 导师在论文修改稿里标注了十几处调整&#xff0c;需要逐一核对 团队协作文档频繁更新&#xff0c;版本差异让人眼花缭乱 传统…

前端浏览器窗口交互完全指南:从基础操作到高级控制

浏览器窗口交互是前端开发中构建复杂Web应用的核心能力&#xff0c;本文深入探讨23种关键交互技术&#xff0c;涵盖从传统API到最新的W3C提案&#xff0c;助您掌握跨窗口、跨标签页的完整控制方案。 一、基础窗口操作体系 1.1 窗口创建与控制 // 新窗口创建&#xff08;现代浏…

Git和Gitlab的部署和操作

一。GIT的基本操作 1.GIT的操作和查看内容 [rootmaster ~]# yum install git -y [rootmaster ~]# git config --list&#xff1a;查看所有配置 2.GIT仓库初始化 [rootmaster ~]# mkdir /gittest&#xff1a;创建目录 [rootmaster ~]# cd /gittest/&#xff1a;进入目录 [rootm…

Linux中线程池的简单实现 -- 线程安全的日志模块,策略模式,线程池的封装设计,单例模式,饿汉式单例模式,懒汉式单例模式

目录 1. 对线程池的理解 1.1 基本概念 1.2 工作原理 1.3 线程池的优点 2. 日志与策略模式 2.1 日志认识 2.2 策略模式 2.2.1 策略模式的概念 2.2.2 工作原理 2.2 自定义日志系统的实现 3. 线程池设计 3.1 简单线程池的设计 3.2 线程安全的单例模式线程池的设计 3…

量子力学:量子通信

量子通信是利用量子力学原理对信息进行编码、传输和处理的新型通信方式&#xff0c;以下是其详细介绍及业界发展现状&#xff1a; 基本原理 量子叠加态 &#xff1a;量子系统可以处于多个状态的叠加&#xff0c;如光子的偏振方向可以同时处于水平和垂直方向的叠加态&#xff…

企业架构之旅(1):TOGAF 基础入门

大家好&#xff0c;我是沛哥儿。今天我们简单聊下TOGAF哈。 文章目录 一、TOGAF 是什么定义与核心定位发展历程与行业地位与其他架构框架的区别 二、TOGAF 核心价值企业数字化转型助力业务与 IT 的协同作用降本增效与风险管控 三、TOGAF 基础术语解析架构域&#xff08;业务、…

CSS 内容超出显示省略号

CSS 内容超出显示省略号 文章目录 CSS 内容超出显示省略号**1. 单行文本省略&#xff08;常用&#xff09;****2. 多行文本省略&#xff08;如 2 行&#xff09;****3. 对非块级元素生效****完整示例****注意事项** 在 CSS 中实现内容超出显示省略号&#xff0c;主要通过控制文…

路由器重分发(OSPF+RIP),RIP充当翻译官,OSPF充当翻译官

路由器重分发&#xff08;OSPFRIP&#xff09; 版本 1 RIP充当翻译官 OSPF路由器只会OSPF语言&#xff1b;RIP路由器充当翻译官就要会OSPF语言和RIP语言&#xff1b;则在RIP中还需要将OSPF翻译成RIPOSPF 把RIP路由器当成翻译官&#xff0c;OSPF路由器就只需要宣告自己的ip&am…

AlexNet网络搭建

AlexNet网络模型搭建 环境准备 首先在某个盘符下创建一个文件夹&#xff0c;就叫AlexNet吧&#xff0c;用来存放源代码。 然后新建一个python文件&#xff0c;就叫plot.py吧&#xff0c;往里面写入以下代码&#xff0c;用于下载数据集&#xff1a; # FashionMNIST里面包含了…

【计算机网络】网络基础概念

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f152; C 语言 | &#x1f310; 计算机网络 这是博主计算机网络的第一篇文章&#xff0c;本文由于是基础概念了解&#xff0c;引用了大…