linux|多线程(一)

news2024/11/14 2:42:32

主要介绍了为什么要有线程 和线程的调用 和简单的对线程进行封装。

背景知识

a.重谈地址空间

我们知道物理内存的最小单元大小是4kB
物理内存是4G那么这样的单元友1M个
操作系统先描述再组织struct page[1M]
对于32位数据字长的机器,页表有2^32条也就是4G条,每一条光保存两个地址加一个标记位 这个页表就得有36G这样大
实际上比如 0010 0010 0010 0010 0010 0010 0010 0010
在这里插入图片描述
虚拟地址>> 12 得到 页框号
虚拟地址&&oxfff 得到 页内偏移量

线程的概念

线程:在进程内部运行,是cpu调度的基本单位
进程承担系统资源的基本实体
在这里插入图片描述
进程承担系统资源的基本实体 怎么理解这句话呢?可以把进程看作家庭,国家的最小单位国是千万家~~家是社会资源分配的最小单位,把线程看作个人,线程是进程的一部分

os要单独设计线程 --新建?暂停?销毁?调度?线程要不要和进程产生关联

在windows 有 专门管理线程的数据结构

struct tcb
{
// 线程id
// 线程 优先级
// 状态上下文
// 链接属性
};

在这里插入图片描述
但是linux 中没有专门的线程,Linux 是用进程模拟的线程
linux中的执行流,我们称为轻量级进程
线程:在进程内部运行,是cpu调度的基本单位
在cpu看来被调用的都是轻量级进程

澄清进程和线程

进程 是 只有一个执行流的线程~

见一见线程

在这里插入图片描述
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

pthread_t *thread: 一个指向pthread类型的指针 , pthread 这个类型用于保存的线程的标识符
const pthread_attr_t *attr: 线程属性对象的引用,可以用来设置线程的优先级、调度策略等。如果不需要设置特殊属性,可以传入 NULL 使用默认属性。
start_routine: 指向线程开始执行的函数的指针这个函数的类型应为 void ()(void*),意味着它接受一个 void * 类型的参数,并返回 void * 类型的结果。
arg: 传递给 start_routine 函数的参数,类型为 void *。这使得你可以在创建线程时向线程函数传递数据。

#include <iostream>
#include <unistd.h>

void *threadStart(void * args)
{
    while(true)
    {
        
        std::cout<<"new thraed running..."<<",pid:"<<getpid()<<std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    // 新线程
    pthread_create(&tid, nullptr, threadStart,(void *) "thread-new"); // 相当于把页表分成了两部分
    // 主线程
    while(true)
    {
        sleep(1);
        std::cout<<"main thraed running..."<<",pid:"<<getpid()<<std::endl;
    }
    return 0;
}

有了多进程为什么还要有多线程

线程不用再创建页表等,只需要一个pcb 创建的成本低
运行期间线程,的调度成本低,因为不需要切换页表
删除线程,比删除一个进程的成本低,只需要删除一个pcb就行了

线程的调度成本为什么更低呢?

我们切换页表只需要修改对应寄存器中的值就可以了,这个不是主要原因
最主要的原因是
在这里插入图片描述
数据会被缓存在cache中,一切换进程,cache中的数据就作废了,就需要重新cache了

为什么还要有进程呢?

线程的健壮性比较低,一个线程出错整个程序都退出了

哪些东西在多线程中是共享的

因为同一地址空间,所以代码段,数据段都是共享的,文件描述符表,每种信号的处理方式,工作目录。

哪些东西是各自都有一份的呢?

线程id,错误码,调度优先级,调度的上下文,信号屏蔽字
**最重要的:**一组寄存器,保存硬件上下文数据 线程是在调度运行的
栈:线程的栈主要用于存储函数调用的局部变量、函数参数以及返回地址。每当线程调用一个新的函数时,相关的局部变量和函数参数就会被压入该线程的栈中,形成一个新的栈帧。当函数返回时,相应的栈帧就会被弹出,恢复之前的调用环境。由于每个线程有独立的栈,它们可以并发地进行函数调用,而不会相互干扰。

几个基本的问题

问题1:主线程和新线程谁先运行?

系统会将这个新线程加入到就绪队列中等待调度。调度器会根据其自身的算法(比如时间片轮转、优先级等)来决定哪个线程获得 CPU 时间片并开始执行。

问题2:我们期望谁最后退出? main thread,你如何保证呢?

我们希望主线程后退出,如果主线程先退出,主线程退出了整个进程就结束了,可能此时新线程还没有完成任务呢。
我们用pthread_join 来保证 主线程后退出

void * RunThread(void * args)
{
    std:: string name;
    name = (const char *)args;
    std::cout <<name << std::endl;
    return args;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, RunThread,(void *)"thread - 1");
    
    int n = pthread_join(tid,nullptr);
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

在这里插入图片描述

问题3: tid 是什么样子的?是什么呢?虚拟地址! 为什么?

void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}

我们打出来发现是一个地址
在这里插入图片描述
为什么是一个地址呢?我们等下再说

问题4:全面看待线程函数传参: 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!

class ThreadData
{
public:
    
    int x;
    int y;
};
void * RunThread(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
    std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    return args;
}
void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}
int main()
{
    pthread_t tid1;
    ThreadData x1;
    x1.x = 10;
    x1.y =20;
   
    pthread_create(&tid1, nullptr, RunThread,(void *)&x1);
  
   
   // PrintToHEX(tid);
    int n = pthread_join(tid1,nullptr);
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

问题6:注意刚刚我们x1的写法其实是不标准的 为什么?

#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:
    
    int x;
    int y;
};
void * RunThread1(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
    data->x = 5;
    data -> y = 10;
    std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    return args;
}
void * RunThread(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
  
    std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    return args;
}
void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}
int main()
{
    pthread_t tid1;
    ThreadData x1;
    x1.x = 10;
    x1.y =20;
   
    pthread_create(&tid1, nullptr, RunThread,(void *)&x1);
    pthread_t tid2;
   
    pthread_create(&tid2, nullptr, RunThread1,(void *)&x1);
    int n = pthread_join(tid1,nullptr);
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

比如说这样,我们原本想要打印 10 ,20 5,10 的 结果却这样。
在这里插入图片描述
因为main 栈上的变量 这两个线程都可以看到并修改,且这个变量只有一份,当第一个线程正准备打印的时候,第二个线程修改了这两个变量的值。导致第一个线程打印出来就和第二个线程一样了。 根本原因就是 变量只有一份,我们多创建一份就好了。

而且我们不推荐直接 将main栈上的变量给新线程因为 main函数栈的变量生命周期是随main函数的,可能新线程早都结束了,但是main栈上的变量就是不结束
我们用new 可以在新线程中方便管理 传过来的变量的生命周期。

问题5: 全面看待线程函数返回

它也可以传递自定义类型哦

#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:
    
    int x;
    int y;
    int Excute()
    {
        return x + y; 
    }
};
class ThreadResult
{
public:
    int x;
    int y;
    int ans;
    void print()
    {
        std::cout<<std::to_string(x)+"+"+std::to_string(y)+"="+std::to_string(ans)<<std::endl;
    }
};

void * RunThread(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
  
    //std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    ThreadResult * result = new ThreadResult();
    result->ans = data->Excute();
    result->x = data->x;
    result->y = data->y;

    return (void*)result;
}
void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}
int main()
{
    pthread_t tid1;
    ThreadData * x1 = new ThreadData();
    x1->x = 10;
    x1->y = 20;
   
    pthread_create(&tid1, nullptr, RunThread,(void *)x1);
    ThreadResult * result =nullptr;
    int n = pthread_join(tid1,(void**)&result);
    result->print();
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

问题6: 如何创建多线程呢?

我们通过循环的方式 创建多线程哦~~

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
void * RunThread(void * args)
{
    std::string name = (const char *) args;
    std::cout<<name<<std::endl;
    return args;
}
int main()
{
    for(int i = 0; i < 10; i++)
    {
        pthread_t tid;
        char name[1024];
        snprintf(name,sizeof(name),"this is %d new thread",i+1);
        pthread_create(&tid, nullptr , RunThread,name);
    }
    sleep(100);
    return 0;
}


在这里插入图片描述
我们发现结果并不符合预期。 因为name 只有一份,主线程 运行的时候不停在覆盖 name。
我们用 new ,循环 new 十次 主线程分配的时候每一个线程都得到 一个地址,那个地址指向一个独立的堆空间,线程之间就不互相影响了。
在这里插入图片描述
ok 完美解决~~!!!

问题7: 新线程如何终止?

1.我们可以在新线程中用return
2.我们可以在新线程中用pthread_exit 注意exit 是终止整个进程。
3.我们可以在新线程中使用pthrad_cancel
线程被取消线程的退出结果是:-1 #define PTHREAD_CANCELED ((void *) -1)

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>

void *RunThread(void *args)
{
    while (true)
    {
        std::string name = (const char *)args;
        std::cout << name << std::endl;
        sleep(3);
    }

    return args;
}

int main()
{
    std::vector<pthread_t> tids;
    for (int i = 0; i < 10; i++)
    {
        pthread_t tid;
        // char name[1024];
        char *name = new char[1024];
        snprintf(name, 1024, "this is %d new thread", i + 1);
        pthread_create(&tid, nullptr, RunThread, name);
        tids.push_back(tid);
    }
    for (auto tid : tids)
    {
        // pthread_detach(tid);
        pthread_cancel(tid);
        void *ret = nullptr;

        int n = pthread_join(tid, &ret);
        std::cout << (long long int)ret << " quit.." << std::endl;
        // std::cout<<"n="<<n<<std::endl;
    }
    return 0;
}

在这里插入图片描述

pthread_cancel 是请求取消另一个线程,而 pthread_exit 是线程自身决定退出。

在这里插入图片描述
在这里插入图片描述
某个线程调用了exit 整个进程就结束了~
exit 你走错片场了啊。要用pthread_exit

void * RunThread(void * args)
{
    std::string name = (const char *) args;
    std::cout<<name<<std::endl;
    pthread_exit(args);
    return args;
}

在这里插入图片描述

问题8: 可以不可以不join线程,让他执行完就退出呢??可以!

我们用pthread_detach 就可以了,然后线程变成unjoinable 状态,如果此时再等待就会出错 然后终止整个进程。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>

void *RunThread(void *args)
{
    while (true)
    {
        std::string name = (const char *)args;
        std::cout << name << std::endl;
        sleep(3);
    }

    return args;
}

int main()
{
    std::vector<pthread_t> tids;
    for (int i = 0; i < 10; i++)
    {
        pthread_t tid;
        // char name[1024];
        char *name = new char[1024];
        snprintf(name, 1024, "this is %d new thread", i + 1);
        pthread_create(&tid, nullptr, RunThread, name);
        tids.push_back(tid);
    }
      for (auto tid : tids)
    {
        pthread_detach(tid); // 主线程分离新线程,新线程必须存在
    }
    for (auto tid : tids)
    {
        // pthread_detach(tid);
        pthread_cancel(tid);
        void *ret = nullptr;

        int n = pthread_join(tid, &ret);
        std::cout << "n:"<<n<<std::endl;
        // std::cout<<"n="<<n<<std::endl;
    }
    return 0;
}

在这里插入图片描述

封装线程

#include <string>
#include <pthread.h>
#include <functional>
namespace ThreadModel
{   
 
    class Thread
    {
    typedef void  (*func_t)(std::string); // ?
   // using func_t = std::function<void()>;
    public:
        Thread(const std::string & name,func_t func)
        :_name(name),_func(func)
        {}
        ~Thread()
        {
            
        }
        void Excute()
        {
            _isrunning = true;
            _func(_name); // 这里传了线程名
            _isrunning = false;

        }
        static void * Routine(void * args) // 不用static 第一个参数是this
        {
            
            Thread * self  = static_cast<Thread*>(args);
          //  self->_func();
            self->Excute();
            return nullptr;
        }

        void start()
        {
            pthread_create(&_tid,nullptr,Routine,(void *)this);
        }
        void join()
        {
           // std::cout<<_isrunning<<std::endl;
          //  std::cout<<true<<std::endl;
            if(_isrunning) // 只有在运行中的线程才需要被等待
            {
                //std::cout<<"..."<<std::endl;
                pthread_join(_tid,nullptr);
            }
          
        }
        // 终止线程
        void stop()
        {
            if(_isrunning)
            {
                _isrunning = false;
                pthread_cancel(_tid);
            }
           
        }
        
    private:
        std::string _name;
        pthread_t _tid;
        func_t _func; 
        bool _isrunning;  // 主要 用于线程终止时 只有启动了的线程 才能终止      
    };
}

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

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

相关文章

springboot的JWT令牌

生成JWT令牌 依赖 <!--jwt令牌--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>javax.xml.bind<…

怎样在 PostgreSQL 中优化对大数据量的分页查询?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 《PostgreSQL 中大数据量分页查询的优化之道》一、理解分页查询的基本原理二、优化分页查询的策略&…

2024年06月CCF-GESP编程能力等级认证C++编程七级真题解析

本文收录于专栏《C等级认证CCF-GESP真题解析》&#xff0c;专栏总目录&#xff1a;点这里。订阅后可阅读专栏内所有文章。 一、单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 第 1 题 下列C代码的输出结果是&#xff08; &#xff09;。 #include <iostr…

SwiftUI 6.0(Xcode 16)新 PreviewModifier 协议让预览调试如虎添翼

概览 用 SwiftUI 框架开发过应用的小伙伴们都知道&#xff0c;SwiftUI 中的视图由各种属性和绑定“扑朔迷离”的缠绕在一起&#xff0c;自成体系。 想要在 Xcode 预览中泰然处之的调试 SwiftUI 视图有时并不是件容易的事。其中&#xff0c;最让人秃头码农们头疼的恐怕就要数如…

Spring Cloud Gateway 自定义断言以及过滤器

1.Spring Cloud gateway介绍 Spring Cloud Gateway 是一个基于 Spring Framework 和 Spring Boot 的 API 网关服务&#xff0c;它利用了 Spring WebFlux 来提供响应式非阻塞式Web请求处理能力。它的核心功能是路由&#xff0c;即根据请求的特定规则将请求转发到后端服务&#…

DP(1500-1700)(刷题)

1.状态机模型&#xff1a;https://codeforces.com/contest/1984/problem/C2 记一下max与min状态转移即可&#xff0c;下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; typedef long long ll; ll a[200010],t,n; ll dp[200010][2];//dp[i][0]表示…

啊?现在不懂 AI ,相当于十年前不懂电脑?

最近有关萝卜快跑的新闻铺天盖地&#xff0c;一篇篇都在唱衰&#xff0c;好像千万滴滴师傅立马就要失业了一样。 还没等多久&#xff0c;在朋友圈看到这样一句话&#xff0c;“现在不懂 AI &#xff0c;相当于十年前不懂电脑”。 我想了许久&#xff0c;最终不得不承认这个事实…

深度学习入门——误差反向传播

要正确理解误差反向传播法&#xff0c;我个人认为有两种方法&#xff1a;一种是基于数学式&#xff1b;另一种是基于计算图&#xff08;computational graph&#xff09; 前者是比较常见的方法&#xff0c;机器学习相关的图书中多数都是以数学式为中心展开论述的。因为这种方法…

达梦数据库的系统视图v$sqltext

达梦数据库的系统视图v$sqltext 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;V$SQLTEXT 是一个系统视图&#xff0c;用于显示当前正在执行或最近执行的SQL语句的文本信息。这个视图对于监控和分析数据库中的SQL活动非常有用&#xff0c;尤其是在需要调试性…

【python】OpenCV—Coordinates Sorted Clockwise

文章目录 1、需求介绍2、算法实现3、完整代码 1、需求介绍 调用 opencv 库&#xff0c;绘制轮廓的矩形边框&#xff0c;坐标顺序为右下→左下→左上→右上&#xff0c;我们实现一下转化为熟悉的 左上→右上→右下→左下 形式 按照这样的顺序组织边界框坐标是执行透视转换或匹…

13. C++继承 | 详解 | 虚拟继承及底层实现

目录 1.定义 1.1继承的概念 1.2 继承的定义 2. 对象赋值转换 3. 继承中的作用域 a. 隐藏/重定义 (Hiding/Redefinition) b. 重载 (Overloading) c. 重写/覆盖 (Overriding) d. 编译报错 (Compilation Error) 4. 派生类的默认成员函数 构造 拷贝构造 运算符重载 析…

处理uniapp刷新后,点击返回按钮跳转到登录页的问题

在使用uniapp的原生返回的按钮时&#xff0c;如果没有刷新会正常返回到对应的页面&#xff0c;如果刷新后会在当前页反复横跳&#xff0c;或者跳转到登录页。那个时候我第一个想法时&#xff1a;使用浏览器的history.back()方法。因为浏览器刷新后还是可以通过右上角的返回按钮…

Vscode+Pyside6开发之虚拟环境配置以及错误解决

Pyside开发之虚拟环境配置以及错误解决 开发环境一、项目创建以及虚拟环境设置1.创建项目2. 新建py文件,新建虚拟环境3.激活虚拟环境二、项目位置改变pip命令报错1.删除原来的虚拟环境2. 产生包列表文件requirements.txt3.重新创建虚拟环境4.重新安装包文件5.其他错误开发环境…

操作系统 输入输出系统

输入输出系统 I/O系统的功能、模型和接口 功能 隐藏物理设备的细节&#xff1a;仅向上层进程提供少量的、抽象的读/写命令 与设备无关性&#xff1a;用户不仅可以使用抽象的I/O命令&#xff0c;还可使用抽象的逻辑设备名来使用设备 提高处理机和I/O设备的利用率&#xff1a;…

IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)

目录 1. 前言 2. 创建SpringBoot项目 3. Maven依赖引入 4. 创建定时任务 5. 问题&#xff1a;执行时间延迟和单线程执行 5.1 问题原因 5.2 解决方式 1. 前言 在现代化应用中&#xff0c;定时任务&#xff08;Scheduled Tasks&#xff09;是不可或缺的一部分&#xff…

pytorch学习(五)tensorboard使用

1. 创建环境 首先创建一个环境: conda create -n pytorch conda activate pytorch 然后安装tensorboard pip install tensorboard 安装opencv pip install opencv-python 2. 简单的案例 标量和图像的显示&#xff1a; 2.1标量实现的方法是add_scalar,第一个参数是给显…

Stable Diffusion:质量高画风清新细节丰富的二次元大模型二次元插图

今天和大家分享一个基于Pony模型训练的二次元模型&#xff1a;二次元插图。关于该模型有4个不同的分支版本。 1.5版本&#xff1a;loar模型&#xff0c;推荐底模型niji-动漫二次元4.5。 xl版本&#xff1a;SDXL模型版本 mix版本&#xff1a;光影减弱&#xff0c;减少SDXL版本…

21天学通C++:第十三、十四章节

第十三章&#xff1a;类型转换运算符 类型转换是一种机制&#xff0c;让程序员能够暂时或永久性改变编译器对对象的解释。注意&#xff0c;这并不意味着程序员改变了对象本身&#xff0c;而只是改变了对对象的解释。可改变对象解释方式的运算符称为类型转换运算符。 为何需要…

数据库端口LookUp功能:从数据库中获取并添加数据到XML

本文将为大家介绍如何使用知行之桥EDI系统数据库端口的Lookup功能&#xff0c;从数据库中获取数据&#xff0c;并添加进输入的XML中。 使用场景&#xff1a;期待以输入xml中的值为判断条件从数据库中获取数据&#xff0c;并添加进输入xml中。 例如&#xff1a;接收到包含采购…

Linux 06-01:简易shell编写

考虑一下这个与shell典型的互动&#xff1a;ls、ps 用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表&#xff0c;它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程&#xff0c;然后在那个进程中运…