Linux:41线程控制lesson29

news2025/4/24 13:34:28

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.cc
	g++ -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.cc
	g++ -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.cc
	g++ -o $@ $^ -std=c++11 -
.PHONY:clean
clean:
	rm -f test_thread

这样就OK

test_thread:TestThread.cc
	g++ -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:线程ID
 value_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.coloradmin.cn/o/2341513.html

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

相关文章

HTMLCSS实现网页轮播图

网页中轮播图区域的实现与解析 在现代网页设计中&#xff0c;轮播图是一种常见且实用的元素&#xff0c;能够在有限的空间内展示多个内容&#xff0c;吸引用户的注意力。下面将对上述代码中轮播图区域的实现方式进行详细介绍。 一、HTML 结构 <div class"carousel-c…

Java基础第21天-正则表达式

正则表达式是对字符串执行模式匹配的技术 如果想灵活的运用正则表达式&#xff0c;必须了解其中各种元字符的功能&#xff0c;元字符从功能上大致分为&#xff1a; 限定符选择匹配符分组组合和反向引用符特殊字符字符匹配符定位符 转义号\\:在我们使用正则表达式去检索某些特…

CSGO 盲盒开箱系统技术实现深度解析

一、系统架构设计 &#xff08;一&#xff09;前后端分离架构 采用前后端分离模式&#xff0c;后端专注业务逻辑处理与数据管理&#xff0c;前端负责用户交互界面呈现。后端通过 RESTful API 与前端进行数据交互&#xff0c;这种架构能有效提高开发效率&#xff0c;便于团队分…

JS通过GetCapabilities获取wms服务元数据信息并在SuperMap iClient3D for WebGL进行叠加显示

获取wms服务元数据信息并在三维webgl客户端进行叠加显示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><tit…

7N60-ASEMI无人机专用功率器件7N60

编辑&#xff1a;LL 7N60-ASEMI无人机专用功率器件7N60 型号&#xff1a;7N60 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 最大漏源电流&#xff1a;7A 漏源击穿电压&#xff1a;600V 批号&#xff1a;最新 RDS&#xff08;ON&#xff09;Max&#xff1a;1.20Ω …

Pytorch图像数据转为Tensor张量

PyTorch的所有模型&#xff08;nn.Module&#xff09;都只接受Tensor格式的输入&#xff0c;所以我们在使用图像数据集时&#xff0c;必须将图像转换为Tensor格式。PyTorch提供了torchvision.transforms模块来处理图像数据集。torchvision.transforms模块提供了一些常用的图像预…

Java 加密与解密:从算法到应用的全面解析

Java 加密与解密&#xff1a;从算法到应用的全面解析 一、加密与解密技术概述 在当今数字化时代&#xff0c;数据安全至关重要。Java 加密与解密技术作为保障数据安全的关键手段&#xff0c;被广泛应用于各个领域。 加密是将明文数据通过特定算法转换为密文&#xff0c;使得…

Java基础系列-HashMap源码解析2-AVL树

文章目录 AVL树左旋右旋左旋右旋的4种情况LL 型RR 型LR 型RL 型 实际插入时怎么判断是那种类型&#xff1f;插入时注意事项删除节点 AVL树 为避免BST树退化成链表的极端情况&#xff0c; AVL 树应运而生。 平衡因子取值&#xff08;-1&#xff0c;0&#xff0c;1&#xff09;…

MySQL的日志--Redo Log【学习笔记】

MySQL的日志--Redo Log 知识来源&#xff1a; 《MySQL是怎样运行的》--- 小孩子4919 MySQL的事务四大特性之一就是持久性&#xff08;Durability&#xff09;。但是底层是如何实现的呢&#xff1f;这就需要我们的Redo Log&#xff08;重做日志&#xff09;闪亮登场了。它记录着…

【AI应用】免费代码仓构建定制版本的ComfyUI应用镜像

免费代码仓构建定制版本的ComfyUI应用镜像 1 创建代码仓1.1 注册登陆1.2 创建代码仓1.5 安装中文语言包1.4 拉取ComfyUI官方代码2 配置参数和预装插件2.1 保留插件和模型的版本控制2.2 克隆插件到代码仓2.2.1 下载插件2.2.2 把插件设置本仓库的子模块管理3 定制Docker镜像3.1 创…

​​Agentic AI——当AI学会主动思考与决策,世界将如何被重塑?

一、引言&#xff1a;2025&#xff0c;Agentic AI的元年 “如果ChatGPT是AI的‘聊天时代’&#xff0c;那么2025年将开启AI的‘行动时代’。”——Global X Insights[1] 随着Agentic AI&#xff08;自主决策型人工智能&#xff09;的崛起&#xff0c;AI系统正从被动应答的“工具…

Ollama API 应用指南

1. 基础信息 默认地址: http://localhost:11434/api数据格式: application/json支持方法: POST&#xff08;主要&#xff09;、GET&#xff08;部分接口&#xff09; 2. 模型管理 API (1) 列出本地模型 端点: GET /api/tags功能: 获取已下载的模型列表。示例:curl http://lo…

PNG透明免抠设计素材大全26000+

在当今的数字设计领域&#xff0c;寻找高质量且易于使用的素材是每个设计师的日常需求。今天&#xff0c;我们将为大家介绍一个超全面的PNG透明免抠设计素材大全&#xff0c;涵盖多种风格、主题和应用场景&#xff0c;无论是平面设计、网页设计还是多媒体制作&#xff0c;都能轻…

4.多表查询

SQL 多表查询&#xff1a;数据整合与分析的强大工具 文章目录 SQL 多表查询&#xff1a;数据整合与分析的强大工具一、 多表查询概述1.1 为什么需要多表查询1.2 多表查询的基本原理 二、 多表查询关系2.1 一对一关系&#xff08;One-to-One&#xff09;示例&#xff1a; 2.2 一…

美团2024年春招第一场笔试 C++

目录 1&#xff0c;小美的平衡矩阵 2&#xff0c;小美的数组询问 3&#xff0c;小美的MT 4&#xff0c;小美的朋友关系 1&#xff0c;小美的平衡矩阵 【题目描述】 给定一个n*n的矩阵&#xff0c;该矩阵只包含数字0和1。对于 每个i(1<i<n)&#xff0c;求在该矩阵中&am…

XHTMLConverter把docx转换html报java.lang.NullPointerException异常

一.报错 1.报错信息 org.apache.poi.xwpf.converter.core.XWPFConverterException: java.lang.NullPointerExceptionat org.apache.poi.xwpf.converter.xhtml.XHTMLConverter.convert(XHTMLConverter.java:77)at org.apache.poi.xwpf.converter.xhtml.XHTMLConverter.doConve…

OpenCV 图形API(52)颜色空间转换-----将 NV12 格式的图像数据转换为 RGB 格式的图像

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从 NV12 (YUV420p) 色彩空间转换为 RGB。该函数将输入图像从 NV12 色彩空间转换到 RGB。Y、U 和 V 通道值的常规范围是 0 到 255。 输出图…

COdeTop-206-反转链表

题目 206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 …

线段树讲解(小进阶)

目录 前言 一、线段树知识回顾 线段树区间加减 区间修改维护&#xff1a; 区间修改的操作&#xff1a; 区间修改update&#xff1a; 线段树的区间查询 区间查询&#xff1a; 区间查询的操作&#xff1a; 递归查询过程&#xff1a; 区间查询query&#xff1a; 代码&…

openharmony5.0.0中C++公共基础类测试-线程相关(一)

C公共基础类测试及源码剖析 延续传统&#xff0c;show me the code&#xff0c;除了给出应用示例还重点分析了下openharmony中的实现。 简介 openharmony中提供了C公共基础类库&#xff0c;为标准系统提供了一些常用的C开发工具类&#xff0c;本文分析其实现&#xff0c;并给…