linux操作系统——线程控制+线程封装

1.理解用户级线程

我们前面用到的所有跟线程有关的接口全部都不是系统直接提供的接口,而是原生线程库pthread提供的接口。我们前面谈到了由于用户只认线程,而linux操作系统是通过用轻量级进程模拟线程,并不是真正的线程,所以linux的解决方案是中间添加一层软件层,向上提供用户所认为的线程接口,向下则调用对应的Linux提供的轻量级进程的接口与用户调用的线程接口与之对应,所以当我们有五个线程时我们都可以看到有5个不同的LWP。Linux天然的就知道进程管理的那一套方法,知道怎么创建,如何被调度,进程状态是运行还是等待,知道怎么回收,怎么终止等等这一系列对进程的管理。那么它又是怎么知道创建了几个线程,而线程的状态是什么呢?创建了几个线程其实我们的用户就知道,所以我们把这种线程叫做用户级线程,因为它是在用户层被实现的,并非在linux操作系统内,linux操作系统只有轻量级进程。本身那些用户级线程就要与linux操作系统下的轻量级进程一一对应,轻量级进程本身也很多被linux操作系统管理起来了,那么线程也相应的需要被管理起来,由谁管理,那么如何管理呢?线程是被pthread库管理起来的,所以要先描述在组织。所以就可以通过一个

struct tcb{

        //lwp id

}        
这样的结构体来进行维护的,然后里面的属性应该直接或间接的与lwp id产生相对应的关系,pthread库会把linux操作系统提供的跟轻量级进程相关的系统调用接口给封装起来,tcb中有些属性这样就可以做到与linux系统产生1:1的对应关系。如果用户层存在很多很多的线程,那么就需要某种数据结构将这些struct tcb对象一个一个的组织起来,所以在库里面我们就可以把线程管理起来。

2.系统调用问题

线程要有独立属性:

a.上下文

线程要有自己独立的上下文,上下文是以轻量级进程的形式维护在PCB当中的。

b.栈

栈在进程地址空间当中其实只有一个,而线程要有自己独立的栈,但是一般都会有多个线程的,那么这个栈该给哪个线程用呢?  其实栈只有一个是因为维护栈结构的寄存器只有一套,有edp,esp,所以每一个线程都有自己独立的栈我们该如何理解呢?

我们知道linux系统中只有轻量级进程的概念,创建轻量级进程的接口是clone,我们下面来认识一下这个接口:

其实这个库当中会在堆空间上申请一段空间充当我们的栈,然后把地址传入到clone接口里面,所以每个新线程的栈在库中维护。其实我们之前谈到的文件有对应的struct file,还有与之对应的文件缓冲区,这个缓冲区是在c语言库中维护的,所以库自己是可以对一段内存空间做维护,同样的道理pthread库也可以维护一段内存空间,所以说新线程的栈是在库中维护的,而我们的库一般是被放在到进程地址空间中的堆栈之间的共享区的,动态库是加载到这里,动态库一旦被加载到物理内存当中,然后通过页表映射到共享区当中,新线程创建的时候,pthread库会在堆中new出来一段空间然后用指针进行维护,给新创建的线程通过clone接口把该地址作为栈的起始地址传进去,这就给线程维护了独立的栈,那么当线程终止的时候只需要把这段空间释放掉就可以了,可是在有新线程之前一定是要先有主线程的,默认地址空间当中的栈是给主线程使用的。

3.理解phtread库管理线程

假设这是我们自己的多线程程序mythread,当我们的mythread允许时会将数据代码加载到内存当中,如果是单纯的打印不用库的程序直接就可以运行了,但是今天我们这个程序使用到了pthread动态库的,要想使用一系列的pthread_create创建线程,pthread_join线程等待,访问pthread_t等这些方法之类的需要先看到我们对应的库,所以动态库也是要被加载到内存中的,然后通过页表映射到进程地址空间的共享区当中,这个库就可以提供各种线程相关的方法属性,操作系统就可以创建相应的PCB了。当然这还只是单进程,考虑到操作系统中有多个进程,多个进程也会创建对应的多线程然后都会用到对应的pthread库,pthread是动态库可以共享的,所以该操作系统内的所有线程都要依赖于这个库,用这同一个pthread库,只需加载到内存中一次就可以一直被使用,所有线程被这个pthread库所管理,所以我们称这个库是共享库。也就是说线程库是共享的,所以内部要管理整个系统,多个用户启动的所有线程。

假设主线程是上图中tid2要通过pthread_join(pthread_t , &ret)获取tid的退出结果。这里的pthrea_t tid是线程属性集合在库中的地址。

4.站在语言角度理解pthread

因为我们知道c++11已经开始支持多线程了,下面我们用一段c++语言的代码来写一段多线程代码:

mythread.cc

#include<iostream>
#include<unistd.h>
#include<thread>
#include<cstdlib>using namespace std;void myrun()
{while(true){cout<<"I am a thread!"<<endl;sleep(1);}
}int main()
{thread t(myrun);t.join();return 0;
}

makefile:

mythread:mythread.ccg++ -o $@ $^ -std=c++11 .PHONY:clean
clean:rm -rf mythread

编译运行之后:

我们发现运行不了,然后我们编译时加上-lpthread

makefile:

mythread:mythread.ccg++ -o $@ $^  -std=c++11 -lpthread.PHONY:clean
clean:rm -rf mythread

然后再编译一次再运行:

发现没有报错了。

我们再打开监视窗口

发现确实是两个线程。

这说明了什么呢?如果不加上-lpthread那么这段代码就运行不了,说明c++11支持的多线程底层封装了pthread库,也是用到原生线程库pthread的。

5.理解线程局部存储

我们直接先用一段代码来进行现象说明:

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>using namespace std;int g_val = 100; // 全局变量,本身就是被所有线程共享的void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);while (true){sleep(1);std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;g_val++;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");while (true){sleep(1);std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;}pthread_join(tid, nullptr);return 0;
}

运行结果:

我们发现g_val的值一旦被新线程改变,主线程立马就能看到,全局变量,本身就是被所有线程共享的。

如果我们在g_val前面加上一个__thread

__thread int g_val = 100; // 全局变量,本身就是被所有线程共享的

其他代码不变的情况下的运行结果:

我们发现主线程的g_val与新线程的g_val的值和地址都不一样,这就是用到了线程的局部存储。

而获取线程的lwp我们可以通过这个接口获取syscall(SYS_gettid);

下面用一段代码来进行获取:


#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>using namespace std;// int g_val = 100; // 全局变量,本身就是被所有线程共享的
__thread int g_val = 100; // 线程的局部存储!有什么用?有什么坑?__thread pid_t lwp = 0;// __thread std::string threadname;pid_t gettid() {return syscall(SYS_gettid);
}void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);lwp = gettid(); // 调用系统调用 SYS_gettid 获取当前线程的 TIDwhile (true){sleep(1);std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;std::cout <<"new thread: " << lwp << std::endl;g_val++;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");lwp = gettid(); // 调用系统调用 SYS_gettid 获取当前线程的 TIDstd::cout <<"main thread: " << lwp << std::endl;while (true){sleep(1);std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;}pthread_join(tid, nullptr);
}

运行结果:

这就获取到了线程的lwp.

6.用c++代码进行线程封装

//thread.hpp
#pragma once
#include<iostream>
#include<string>
#include<functional>
#include<pthread.h>
template<class T>
using func_t = std::function<void(T)>;template<class T>
class Thread
{
public:Thread(const std::string & threadname,func_t<T> func,T data):_tid(0),_isrunning(false),_threadname(threadname),_func(func),_data(data){}static void* ThreadRoutine(void * args){Thread* ts =static_cast<Thread*>(args);ts->_func(ts->_data);return nullptr;}bool Start(){int n = pthread_create(&_tid,nullptr,ThreadRoutine,this);if(n==0){_isrunning = true;return true;}return false;}bool Join(){if(!_isrunning) return true;int n = pthread_join(_tid,nullptr);if(n==0){_isrunning = false;return true;}return false;}   std::string ThreadName(){return _threadname;}bool IsRunning(){return _isrunning;}~Thread(){}private:pthread_t _tid;bool _isrunning;std::string _threadname;func_t<T> _func;T _data;
};

//main.cc
#include<iostream>
#include<string> 
#include<unistd.h>
#include"Thread.hpp"std::string GetThreadName()
{static int num = 1;char name[64];snprintf(name,sizeof(name),"Thread-%d",num++);return name;
}void Print(int num)
{while(num){std::cout<<"hello world: "<<num--<<std::endl;sleep(1);}
}int main()
{Thread<int> t(GetThreadName(),Print,10);std::cout<<"thread is running? "<<t.IsRunning()<<std::endl;t.Start();std::cout<<"thread is running? "<<t.IsRunning()<<std::endl;t.Join();return 0;
}

Makefile

thread_test:main.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean
clean:rm -rf thread_test

运行结果:

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

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

相关文章

C# for/foreach 循环

一个 for 循环是一个允许您编写一个执行特定次数的循环的重复控制结构。 语法 C# 中 for 循环的语法&#xff1a; for ( init; condition; increment ) {statement(s); } 下面是 for 循环的控制流&#xff1a; init 会首先被执行&#xff0c;且只会执行一次。这一步允许您声…

【数据结构】非线性结构——二叉树

文章目录 前言1.树型结构1.1树的概念1.2树的特性1.3树的一些性质1.4树的一些表示形式1.5树的应用2.二叉树 2.1 概念2.2 两种特殊的二叉树2.3 二叉树的性质2.4 二叉树的存储2.5 二叉树的基本操作 前言 前面我们都是学的线性结构的数据结构&#xff0c;接下来我们就需要来学习非…

Stable Diffusion 进阶教程 - 二次开发(制作您的文生图应用)

目录 1. 引言 2. 基于Rest API 开发 2.1 前置条件 2.2 代码实现 2.3 效果演示 2.4 常见错误 3. 总结 1. 引言 Stable Diffusion作为一种强大的文本到图像生成模型&#xff0c;已经在艺术、设计和创意领域引起了广泛的关注和应用。然而&#xff0c;对于许多开发者来说&#xff…

iOS开发进阶(九):OC混合开发嵌套H5应用并互相通信

文章目录 一、前言二、嵌套H5应用并实现双方通信2.1 WKWebView 与JS 原生交互2.1.1 H5页面嵌套2.1.2 常用代理方法2.1.3 OC调用JS方法2.1.4 JS调用OC方法 2.2 JSCore 实现原生与H5交互2.2.1 OC调用H5方法并传参2.2.2 H5给OC传参 2.3 UIWebView的基本用法2.3.1 H5页面嵌套2.3.2 …

Spring Boot | SpringBoo“开发入门“

目录 : 1.SpringBoot的“介绍”SpringBoot”概述” &#xff1a;SpringBoot”简介“SpringBoot的“优点” 2. SpringBoot入门程序环境准备使用 “Maven”方式构建SpringBoot 项目使用“Spring Initializr”方式构建Spring Boot 项目 3. “单元测试” 和“热部署”单元测试热部署…

微服务day06 -- Elasticsearch的数据搜索功能。分别使用DSL和RestClient实现搜索

1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有数据&#xff0c;一…

‘npm‘ 不是内部或外部命令,也不是可运行的程序

npm认识三年了&#xff0c;今天才知道这是node.js的命令 也就是说&#xff0c;想要在cmd里面运行 npm 命令&#xff0c;但就的安装node.js 1. node.js安装 没有安装包的先下载安装包&#xff1a;下载 | Node.js 中文网 (nodejs.cn) 下载之后双击打开&#xff0c;一路安装确…

基于Arduino IDE 野火ESP8266模块 EEPROM 存储开发

一、操作存储器 我们可以使用ESP8266模块的EEPROM&#xff0c;也就是可读可擦存储器&#xff0c;可以掉电不丢失地帮我们存储一些数据。ESP8266微控制器有一个闪存区(Flash memory) 来模拟Arduino的EEPROM。这是微控制器中一个特殊的内存位置&#xff0c;即使在主板关闭后&…

vscode添加gitee

1.创建仓库 2.Git 全局设置 3.初始化仓库 2.1 打开vscode打开需要上传到给git的代码文件 2.2.点击左边菜单第三个的源代码管理->初始化仓库 4.点击加号暂存所有更改 5.添加远程仓库 5.1 添加地址&#xff0c;回车 5.2 填写库名&#xff0c;回车 6.提交和推送 6.1 点击✔提交…

SpringBoot学习之ElasticSearch下载安装和启动(Mac版)(三十一)

本篇是接上一篇Windows版本,需要Windows版本的请看上一篇,这里我们继续把Elasticsearch简称为ES,以下都是这样。 一、下载 登录Elasticsearch官网,地址是:Download Elasticsearch | Elastic 进入以后,网页会自动识别系统给你提示Mac版本的下载链接按钮 二、安装 下载…

【分布式】——CAPBASE理论

CAP&BASE理论 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/tree-learning-notes ⭐⭐⭐⭐⭐⭐ Spring专栏&#x1f449;https://blog.csdn.net/weixin_53580595/category_12279588.html Sprin…

JMeter元件作用域和执行顺序

JMeter元件作用域和执行顺序 元件的基本介绍基本元件总结 作用域的基本介绍作用域的原则元件执行顺序Jmeter第一个案例&#xff1a; Jmeter三个重要组件&#xff08;重点&#xff09;线程组特点线程组分类线程组的属性案例分析 HTTP请求案例一&#xff08;使用HTTP请求路径来传…

基于ArkUI框架开发-ImageKnife渲染层重构

ImageKnife是一款图像加载缓存库&#xff0c;主要功能特性如下&#xff1a; ●支持内存缓存&#xff0c;使用LRUCache算法&#xff0c;对图片数据进行内存缓存。 ●支持磁盘缓存&#xff0c;对于下载图片会保存一份至磁盘当中。 ●支持进行图片变换&#xff1a;支持图像像素…

el-card设置内边距

el-card设置内边距 :deep(.el-card .el-card__body) {padding: 5px; }

java第一次作业(二)

先写思路&#xff0c;再写代码&#xff0c;思路清晰&#xff0c;才能写对代码 7-6 求12...n的和 思路&#xff1a; 运用expression的字符串输出 重点&#xff1a; expression输出 代码&#xff1a; import java.util.Scanner; public class Main {public static void main…

【代驾+顺风车+货运】全开源双端APP代驾+顺风车+货运代驾小程序源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 一、详细介绍 系统是基于Thinkphpuniapp开发的&#xff0c;全开源未加密&#xff0c;这套源码可以拿回去自己做二开 后台用户端司机端 功能详情介绍&#xff1a; 车主实名认证&#xff0c;驾驶证认证&#xff0c;车…

MySQL8.0.27(社区版)安装教程和环境变量配置

MySQL8.0.27&#xff08;社区版&#xff09;软件百度网盘链接 链接&#xff1a;https://pan.baidu.com/s/1SRS0hAQQBENQFTfLeDjYzw?pwd1111 提取码&#xff1a;1111 下载完成后&#xff0c;解压MySQL社区版的ZIP压缩文件&#xff0c;通过命令的方式安装MySQL 1.安装MySQL8.0…

程序汪若依微服务华为云Linux部署保姆教程

若依官方有3个版本&#xff0c;程序汪以前已经出了对应的安装部署视频教程 单应用版本 前后分离版本 微服务版本 本视频是若依微服务版本&#xff0c;如果基础的环境软件都不会安装建议看下程序汪的单应用和前后端分离版本教程&#xff0c; 欢迎点击进入 &#xff08;单应…

字符驱动程序-LCD驱动开发

一、驱动程序的框架 总共分为五步&#xff1a; 1、自己设定或者系统分配一个主设备号 2、创建一个file_operations结构体 这个结构体中有操作硬件的函数&#xff0c;比如drv_open、drv_read 3、写一个注册设备驱动函数 需要register_chrdev(major,name,结构体)&#xff0…

各种需要使用的方法-->vue/微信小程序/layui

各种需要使用的方法-->vue/微信小程序/layui 1、vue里样式不起作用的方法&#xff0c;可以通过deep穿透的方式2、 js获取本周、上周、本月、上月日期3、ArrayBuffer Blob 格式转换ArrayBuffer与Blob的区别ArrayBuffer转BlobBlob转ArrayBuffer需要借助fileReader对象 4、使用…