【Linux系统化学习】线程控制

news/2024/5/19 2:28:45

目录

前言

POSIX线程库

线程控制

创建线程

线程终止

pthread_exit()函数

pthread_cancel()函数(会在下面线程等待部分详解)

线程等待 

pthread_join()函数

获取线程退出码

分离线程

线程取消(pthread_cancel()函数)

线程ID及进程地址空间分布


前言

好久不见!各位支持我的IT人,前段时间由于准备蓝桥杯需要学习一段时间算法就停止更新Linux的内容;由于自己也是算法初学者,想着蓝桥杯突击一个多月算法就行了没必要更新算法博客,后面还会专门学习算法到那时候在更新算法的内容。停更前对Linux中的线程做了一个简单的介绍,在接下来的几篇文章我们会深入探讨Linux的线程这方面的问题。线程这方面学习完后,Linux系统编程就结束了,接下来便是网络编程和数据库;在网络编程学习中会编写我的第一个项目,到时候希望大家多多支持!!!


POSIX线程库

线程是操作系统中的一个概念,在Linux这个特定的操作系统中是没有线程这个概念的;是用轻量级进程(LWP)来实现所谓操作系统中的线程的;因此Linux操作系统只会提供轻量级进程的的系统调用,不会提供线程创建的接口。基于此原因我们要在操作系统和用户之间包含一个软件层对用户层向上提供线程的控制接口,对操作系统提供轻量级进程的控制接口。这并不繁琐,可以让操作系统和用户层实现解耦,反而这是Linux操作系统的一大亮点。这个软件层就是POSIX线程库,这是一个原生库,对于每个Linux系统都会包含这个库;因此我们在用户层创建线程时需要引入这个库。


线程控制

创建线程

函数原型

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

功能:创建一个新的线程

参数:

  • thread:返回线程ID(这是一个输出行参数)
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数

注意:第三个参数是一个返回值为void*参数为void*的函数指针,第四个void*参数作为第三个函数指针的参数。 

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

简单代码示例

void* ThereadTountine(void* args)
{const char * p = (char *) args;//这里也可以使用c++11中的安全类型转换while(true){sleep(1);cout<<p<<endl;}
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThereadTountine,(void*)"new thread-1");while(true){sleep(1);cout<<"main thread"<<endl;}return 0;
}

  

 这里只是创建了一个新线程,我们可以使用循环配合这个线程创建函数创建更多的线程。

注意:这里的第四个参数不仅仅可以像上面一样传入一个常量字符串,甚至是可以传入一个自定义对象。

线程终止

如果我们只终止某个线程而不终止整个进程,我们可以有三种方法

  • 1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  • 2. 线程可以调用pthread_ exit终止自己。(新线程调用的函数不可以使用exit函数,因为exit使用来终止整个进程的,而线程只是整个进程中的一个执行流)
  • 3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

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;失败返回错误码 

获取线程退出码

线程创建函数的返回值为void * ,而线程等待的第二个参数为void**;这个参数是一个输出型参数,可以获取到函数退出的推出信息。

void* ThereadTountine(void* args)
{const char * p = (char *) args;//这里也可以使用c++11中的安全类型转换int cnt=5;while(cnt--){sleep(1);cout<<p<<endl;}return (void*)"new thread-1 done";
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThereadTountine,(void*)"I am new thread-1");void * ret=nullptr;int n = pthread_join(tid,&ret);cout<<(const char *) ret<<endl;return 0;
}

对于线程出异常我们根本不许用考虑,因为当某个线程出异常的时候,整个进程都会挂掉;等待线程已经没有什么意义了。

分离线程

如果我们线程一直不退出时,主线程会一直阻塞等待这个线程。我们可以将这个线程设置为游离状态。

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

因此我们可以根据需求动态决策,线程的状态。 

函数原型

如果想要分离一个线程,可以在主线程中调用函数将新线程分离,也可以在新线程中将自已与主线程分离。

int pthread_detach(pthread_t thread);//分离自己
pthread_detach(pthread_self());

注意:joinable和分离是冲突的,一个线程不可以既是joinable又是分离的。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void *ThereadTountine(void *args)
{// 新线程和主线程分离//pthread_detach(pthread_self());const char *p = (char *)args; // 这里也可以使用c++11中的安全类型转换int cnt = 5;while (cnt--){sleep(1);cout << p << endl;}return (void *)"new thread-1 done";
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThereadTountine, (void *)"I am new thread-1,running  ~ ~ ");//主线程和新线程分离pthread_detach(tid);sleep(1);//保证新线程已经执行了一秒int n = pthread_join(tid, nullptr);cout << n << endl;return 0;
}

 

线程取消(pthread_cancel()函数)

void *ThereadTountine(void *args)
{const char *p = (char *)args; // 这里也可以使用c++11中的安全类型转换int cnt = 5;while (cnt--){sleep(1);cout << p << endl;}
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThereadTountine, (void *)"I am new thread-1,running  ~ ~ ");sleep(5);//保证新线程已经执行了一秒//取消新线程int n = pthread_cancel(tid);cout<<"cancle n:"<<n<<endl;void* ret=nullptr;n = pthread_join(tid, &ret);cout<<"join n:" << n << "    thread return:"<<(int64_t) ret<<endl;return 0;
}

 

当线程被取消后其退出码为-1;当线程被分离时可以被取消,但是不可以被等待。因此对于分离的线程在运行时我们可以对其取消操作


线程ID及进程地址空间分布

在新线程中可以使用pthread_self(),来获取自身ID

 这个ID看起来非常的大,我们可以尝试转化为16进制进行打印; 

转化为十六进制后,这个线程ID和虚拟地址非常的类似。

void* ThereadTountine(void* args)
{const char * p = (char *) args;//这里也可以使用c++11中的安全类型转换while(true){sleep(1);cout<<p<<endl;printf("new thread ID : %x\n",pthread_self());//获取自身线程ID}
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThereadTountine,(void*)"I am new thread-1");while(true){sleep(1);cout<<"I am main thread"<<endl;printf("main ID : %x\n",pthread_self());}return 0;
}

当我们在用户层创建了n个线程时,通过线程库操作系统会创建n个轻量级进程,对于这些轻量级进程来说可以复用进程那一套管理方案来解决。我们所谓的“线程”是不过是用户级线程,对于这些线程来说我们也要进行管理——“先描述,在组织”,因此我们要有一个TCB(Thread Control Block,线程控制块),这个TCB并不存在于操作系统中,存在于库中。TCB中含有LWP ID 等关于这个线程的各种属性。但是对于每个单独的线程来说,还要包含存储独立上下文的存储结构和独立的栈空间。库是共享的在共享区中只包含一次就可以,然而线程可以是很多的,我们可以将每个线程的TCB和进程上下文的存储空间和独立的栈描述起来,然后使用一个数组将其组织起来;将每个线程的起始地址作为TID向上返回给用户。

两个小问题

线程可以fork()创建子进程吗?

可以,在前面进程的学习中我们是只含有一个执行流的进程,在这个进程中创建子进程;现在我们这个进程中的进程中含有多个执行流,只不过在这众多执行流中fork子进程。

线程可以进行程序替换吗?

可以,但不推荐!因为线程公用主线程的公共资源,进行程序替换可能会导致,整个进程被替换;应该在众多线程中的一个线程中fork一个进程后进行程序替换。 


今天对Linux下线程控制的各种操作的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!!


http://www.mrgr.cn/p/50683233

相关文章

打印文件 -批量打印PDF/WORD/EXCEL/POWER POINT文件

打印软件下载地址 链接:https://pan.baidu.com/s/1IjRlNb2Krl8P_pCuIhbL-g 提取码:gzkn --来自百度网盘超级会员V4的分享 批量打印PDF/WORD/EXCEL/POWER POINT文件 | SW技巧网 (peesky.com)

结对作业第一天

<div style="width: 100%; font-family: 微软雅黑; text-align: center; font-size: 20pt; ">石家庄铁道大学北京地铁查询系统</div><br/><div id="localtime" style="text-align: center;"></div><div id=&qu…

【C++类和对象】初始化列表与隐式类型转换

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

使用Python进行容器编排Docker Compose与Kubernetes的比较

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 随着容器化技术的普及&#xff0c;容器编排成为了管理和部署容器化应用程序的重要环节。在容…

SnakeYaml反序列化分析

前言 SnakeYaml是Java中解析yaml的库,而yaml是一种人类可读的数据序列化语言,通常用于编写配置文件等。yaml真是到哪都有啊。 环境搭建 <dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.32</v…

使用51单片机控制T0和T1分别间隔1秒2秒亮灭逻辑

#include <reg51.h>sbit LED1 P1^0; // 设置LED1灯的接口 sbit LED2 P1^1; // 设置LED2灯的接口unsigned int cnt1 0; // 设置LED1灯的定时器溢出次数 unsigned int cnt2 0; // 设置LED2灯的定时器溢出次数// 定时器T0 void Init_Timer0() {TMOD | 0x01;; // 定时器…

去除图像周围的0像素,调整大小

在做分割任务时&#xff0c;经常需要处理图像&#xff0c;如果图像周围有一圈0像素&#xff0c;需要去除掉&#xff0c;重新调整大小 数组的处理 如果图像的最外一圈为0&#xff0c;我们将图像最外圈的图像0去除掉。 import numpy as npdef remove_outer_zeros(arr):# 获取数…

定时器、PWM定时器、UART串口通信

我要成为嵌入式高手之4月15日ARM第八天&#xff01;&#xff01; ———————————————————————————— 定时器 S3C2440A 有 5 个 16 位定时器。其中定时器 0、1、2 和 3 具有脉宽调制&#xff08;PWM&#xff09;功能。定时器 4 是一个无 输出引脚的内部…

每日两题 / 438. 找到字符串中所有字母异位词 238. 除自身以外数组的乘积(LeetCode热题100)

438. 找到字符串中所有字母异位词 - 力扣&#xff08;LeetCode&#xff09; 记录p串每个字符出现次数 维护与p串等长的滑动窗口&#xff0c;记录其中每个字符的出现次数 每次滑动后将当前次数与p串的次数比较即可 class Solution { public:vector<int> findAnagrams(s…

Nginx第3篇-使用ngx_http_proxy_connect_module配置https正向代理

场景 我使用python爬虫&#xff0c;然后需要个代理&#xff0c;所以就用Nginx搭了一个代理服务器。对Nginx也不太熟&#xff0c;慢慢摸索&#xff0c;搭建完之后发现只能代理http的请求&#xff0c;无法穿透https。几经折腾和摸索发现一个强大的HTTP代理模块&#xff1a;ngx_h…

水资源管理系统:守护生命之源,构建和谐水生态

水资源是维系地球生态平衡和人类社会可持续发展的重要基础。然而,随着人口增长、工业化和城市化的加速,水资源短缺、水质污染和生态破坏等问题日益凸显。在这样的背景下,构建一个全面、高效、智能的水资源管理系统显得尤为迫切和必要。 项目背景 水资源的合理利用和有效保护…

Docker构建Golang项目常见问题

Docker构建Golang项目常见问题 1 Dockerfile1.1 dockerfile报错&#xff1a;failed to read expected number of bytes: unexpected EOF1.2 go mod tidy: go.mod file indicates go 1.21, but maximum supported version is 1.171.3 是否指定启动文件问题 2 构建及部署 1 Docke…

Unity3D 爆火的休闲益智游戏工程源码/3D资源 大合集

Unity3D休闲益智游戏工程源码大合集 一、关卡类游戏工程源码二、跑酷类游戏工程源码三、消除合成类游戏工程源码四、棋牌类游戏工程源码五、RPG(角色扮演)类游戏工程源码六、FPS&#xff08;射击&#xff09;类游戏工程源码十、Unity3D工艺仿真六、Unity游戏资源1、Unity3D 吃鸡…

vis.js外部自定义折线图

代码案例<!doctype html> <html> <head><title>Timeline</title><script type="text/javascript" src="https://unpkg.com/vis-timeline@latest/standalone/umd/vis-timeline-graph2d.min.js"></script><lin…

react native 安装app时报错 ”已安装了签名冲突的应用“

1. 问题描述: react native开发完app,手动安装app,报错”已安装了签名冲突的应用“。 或者执行命令安装npx react-native run-android --mode=release,报错2. 解决方法: 直接卸载原来的app发现无效,于是执行: adb uninstall "xxxxx"xxxxx换成你的app名,在这里…

SLS 查询新范式:使用 SPL 对日志进行交互式探索

在构建现代数据和业务系统的过程中,可观测性已经变得至关重要,日志服务(SLS)为 Log/Trace/Metric 数据提供了大规模、低成本、高性能的一站式平台服务,并提供数据采集、加工、投递、分析、告警、可视化等功能,从而全面提升企业在研发、运维、运营和安全等各种场景的数字化…

MySQL 基础语法(2)

文章目录 创建表查看表修改表表数据插入 本文为表结构相关的基础语言库相关的基础语句 创建表 CREATE TABLE table_name ( field1 datatype comment xxx, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎;CREATE TABLE&#xff1…

性能测试——压测工具locust——脚本初步编写

User Class 一个用户类代表一个用户(如果你愿意,也可以是一群蝗虫)。Locust 将为正在模拟的每个用户生成一个 User 类的实例。用户类可以定义一些通用属性。on_start 和 on_stop 方法 User和TaskSets可以声明一个on_start和on_stop方法, User:在该用户开始运行时调用on_st…