Linux线程控制
本篇我将学习如何使用多线程。要使用多线程,因为Linux没有给一般用户直接提供操作线程的接口,我们使用的接口,都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此,需要引入-lpthread,即连接原生线程库。
原生线程库:#include <pthread.h>
自动化构建工具:
mythread:mythread.cgcc -o $@ $^ -lpthread
.PHONY:clean
clean:rm -f mythread
创建线程
创建线程。
功能:创建一个新的线程
原型: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 : 传给线程启动函数的参数.
返回值:成功返回0;失败返回错误码.获取调用它的线程id。即哪个线程调用了它,就能够获得自己的id。
函数原型:pthread_t pthread_self(void);
功能:获取一个线程id,即谁调用它,就获取谁的线程id
头文件:#include <pthread.h>
参数:无
返回值:成功返回这个id。这个函数总是成功的!创建一个线程,代码如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *thread_run(void *args)
{while(1){//使用pthread_self()获取idprintf("我是新线程[%s],我的线程ID是: %lu\n",(const char*)args,pthread_self());sleep(1);}
}int main()
{pthread_t tid;//第一个参数为线程id,第二个为线程属性,设置为NULL默认值//第三个参数是线程执行的方法,第四个是传给第三个参数的参数pthread_create(&tid,NULL,thread_run,(void*)"new thread");while(1){printf("我是主线程,我创建的线程ID是: %lu,我的线程ID是: %lu\n",tid,pthread_self());sleep(1);}return 0;
}结果如下,能看到两个线程的ID不一样,那就证明了单个进程中存在着两个线程。

创建多个线程,代码如下:
使用数组来存放线程id。注意此时thread_run()函数被重入了!
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *thread_run(void *args)
{while(1){//使用pthread_self()获取id// printf("我是新线程[%s],我创建的线程ID是: %lu\n",(const char*)args,pthread_self());sleep(3);}
}int main()
{//创建五个线程,保存在数组中pthread_t tid[5];//第一个参数为线程id,第二个为线程属性,设置为NULL默认值//第三个参数是线程执行的方法,第四个是传给第三个参数的参数int i = 0;for(i = 0;i<5;++i){pthread_create(tid+1,NULL,thread_run,(void*)"new thread");}while(1){printf("我是主线程,我的thread ID:%lu\n",pthread_self());printf("######################begin########################\n");for(i = 0;i<5;++i){  printf("我是主线程,我创建的线程[%d]ID是: %lu,我的线程ID是: %lu\n",i,tid[i],pthread_self());}printf("######################end######################\n");sleep(1);}return 0;
}结果如下:

通过ps -aL查看当前线程。可以看到,PID和LWP相同的就是主线程,其它的都是新线程。LWP是线程id。

线程等待
一般而言,线程也是需要等待的,如果不等待,就可能会导致类似于"僵尸进程"的问题。
功能:等待线程结束
原型:int pthread_join(pthread_t thread, void** value_ptr);
参数:
thread : 线程ID
value_ptr : 它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码写一个简单的测试,主线程在等待,10秒后打印111.
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *thread_run(void *args)
{int num = *(int*)args;while(1){//使用pthread_self()获取idprintf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());sleep(10);break;}//随便返回一个值用于测试return (void*)111;
}#define NUM 1
int main()
{pthread_t tid[NUM];int i = 0;for(i = 0;i<NUM;++i){pthread_create(tid+1,NULL,thread_run,(void*)&i);sleep(1);}//指针变量可以当某个数据的容器void *status = NULL;//获得退出信息pthread_join(tid[0],&status);printf("ret: %d\n",(int)status);return 0;
}线程只能一个个等。
线程终止
线程终止的方案有:
1.函数中的return。对于这个方案有两种情况:第一种情况是在main函数中的return,此时代表进程和主线程都退出了。第二种情况是其它函数中的return,代表该线程的退出。
2.使用函数pthread_exit().
功能:线程终止
原型:void pthread_exit(void* value_ptr);
参数:
value_ptr : z指的是退出后的返回值,也就是return X。value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *thread_run(void *args)
{int num = *(int*)args;while(1){//使用pthread_self()获取idprintf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());sleep(2);break;}pthread_exit((void*)123);
}#define NUM 1
int main()
{    pthread_t tid[NUM];int i = 0;for(i = 0;i<NUM;++i){pthread_create(tid+1,NULL,thread_run,(void*)&i);sleep(1);}//指针变量可以当某个数据的容器void *status = NULL;//获得退出信息pthread_join(tid[0],&status);printf("ret: %d\n",(int)status);return 0;
}如果使用exit(),那么会将进程和全部线程都终止掉。
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,函数退出代表函数栈帧被销毁,从而这个内存单元也被销毁了。
3.使用pthread_cancel函数取消目标线程。
功能:取消一个执行中的线程
原型:int pthread_cancel(pthread_t thread);
参数:
thread : 线程ID
返回值:成功返回0;失败返回错误码,退出码为-1
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *thread_run(void *args)
{int num = *(int*)args;while(1){//使用pthread_self()获取idprintf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());sleep(2);//break;}//pthread_exit((void*)123);//随便返回一个值用于测试//return (void*)111;
}#define NUM 1
int main()
{pthread_t tid[NUM];int i = 0;for(i = 0;i<NUM;++i){pthread_create(tid+1,NULL,thread_run,(void*)&i);sleep(1);}printf("wait sub thread...\n");//等5秒钟sleep(5);printf("cancel sub thread...\n");//取消线程pthread_cancel(tid[0]);//指针变量可以当某个数据的容器void *status = NULL;//获得退出信息pthread_join(tid[0],&status);printf("ret: %d\n",(int)status);return 0;
}当一个新线程被取消后,退出码为-1,即PTHREAD_ CANCELED。
当把主线程取消,但新线程没有被取消,此时新线程依旧在运行着,并且主线程会进入"僵尸状态"(说明:线程没有僵尸状态这个东东,是有类似僵尸进程的问题)。因此我们一般不能用新线程去取消主线程。

线程分离
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
线程分离后,不需要被join终止,只需运行结束后会自动释放Z。分离后的线程相对于是同一屋檐下的陌生人,即这个线程在跟同一个进程内的线程毫无关系了,此时一定不能对其join,因为会失败。
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
线程分离一般的应用场景是主线程不退出,新线程处理完任务后退出。
功能:分离线程
原型:int pthread_detach(pthread_t thread);
参数:
thread : 线程ID
返回值:成功返回0;#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>void *thread_run(void *args)
{//分离pthread_detach(pthread_self());int num = *(int*)args;while(1){//使用pthread_self()获取idprintf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());sleep(2);break;}//随便返回一个值用于测试return (void*)111;
}#define NUM 1
int main()
{pthread_t tid[NUM];int i = 0;for(i = 0;i<NUM;++i){pthread_create(tid+1,NULL,thread_run,(void*)&i);sleep(1);}printf("wait sub thread...\n");//等5秒钟sleep(5);printf("cancel sub thread...\n");//指针变量可以当某个数据的容器void *status = NULL;int ret = 0;//获得退出信息ret = pthread_join(tid[0],&status);printf("ret: %d,status: %d\n",ret,(int)status);return 0;
}
LPW的解释

在使用ps -aL查看线程情况时,LWP为内核LWP,我们最好不要叫它线程ID,因为在Linux中没有线程这玩意,我们所说的线程,都是进程PCB模拟出来的,属于轻量级进程。
对于LWP,它的值跟我们在测试代码时得出的结果(线程的ID)不一样,一个是原生线程库的,一个是内核的。
下面将好好分析一下,原生线程库中的"线程pid"的本质。
先来说结果,我们通过pthread_self()获取的线程id,其实是虚拟内存地址!

我们都知道,每一个线程都要有运行的临时数据,因此每个线程都要有自己的私有栈结构!也需要拥有描述线程的用户控制块!但是在虚拟地址空间中的栈结构,不可能会分成很多份给每一个线程的,它是属于主线程和进程的!
每一个新线程所拥有的栈结构等等,其实都是由原生线程库提供的!每一个线程跟每一个库提供的线程栈和线程局部存储等组成的用户控制块都是一一对应的,是以1:1的比例对对应着!
那么如何区找到需要找到的线程,就需要用到一个地址去找,并且每一个描述线程的用户控制块都会保存着每一个线程对应的PWD!这个地址就是每一个用户控制块的地址!
总结:
①pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和内核LWP不是一回事,前者是原生线程库中的,一个是内核LWP。
②前面讲的LWP(线程ID)属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
③pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
④线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID。
相关文章:
 
Linux线程控制
本篇我将学习如何使用多线程。要使用多线程,因为Linux没有给一般用户直接提供操作线程的接口,我们使用的接口,都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此,需要引入-lpthread,即连接原生…...
 
【LeetCode】剑指 Offer(20)
目录 题目:剑指 Offer 38. 字符串的排列 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 38. 字符串的…...
 
FutureTask中的outcome字段是如何保证可见性的?
最近在阅读FutureTask的源码是发现了一个问题那就是源码中封装结果的字段并没有使用volatile修饰,源码如下:public class FutureTask<V> implements RunnableFuture<V> {/*** 状态变化路径* Possible state transitions:* NEW -> COMPLET…...
 
直播回顾 | 聚焦科技自立自强,Bonree ONE 助力国产办公自动化平稳替代
3月5日,两会发布《政府工作报告》,强调科技政策要聚焦自立自强。 统计显示,2022年金融信创项目数同比增长300%,金融领域信创建设当前已进入发展爆发期,由国有大型银行逐渐向中小型银行、非银金融机构不断扩展。信创云…...
 
深入理解Linux进程
进程参数和环境变量的意义一般情况下,子进程的创建是为了解决某个问题。那么解决问题什么问题呢?这个就需要进程参数和环境变量来进行决定的。子进程解决问题需要父进程的“数据输入”(进程参数 & 环境变量)设计原则:3.1 子进程启动的时候…...
Vue3之组件间的双向绑定
何为组件间双向绑定 我们都知道当父组件改变了某个值后,如果这个值传给了子组件,那么子组件也会自动跟着改变,但是这是单向的,使用v-bind的方式,即子组件可以使用父组件的值,但是不能改变这个值。组件间的…...
 
Java语法基础(一)
目录 代码注释方法 编码规范 基本数据类型及取值范围 变量和常量的声明与赋值 变量 常量 标识符 基本数据类型的使用 整数类型的使用 浮点类型的使用 布尔类型的使用 字符类型的使用 代码注释方法 单行注释:使用“//”进行单行注释多行注释:使…...
 
优思学院|零质量控制是什么概念?
零质量控制(Zero Quality Control)是指一个理想的系统,可以生产没有任何缺陷的产品,因此不需要频繁的检查,从而节省时间和金钱。那些追求过程优化并致力于持续过程改进的组织将零质量控制(Zero Quality Con…...
2023-03-09 CMU15445-Query Execution
摘要: CMU15445, Project #3 - Query Execution 参考: Project #3 - Query Execution | CMU 15-445/645 :: Intro to Database Systems (Fall 2022) https://github.com/cmu-db/bustub 要求: OVERVIEW At this point in the semester, you have implemented the internal co…...
 
vuedraggable的使用
Draggable为基于Sortable.js的vue组件,用以实现拖拽功能。 特性 支持触摸设备 支持拖拽和选择文本 支持智能滚动 支持不同列表之间的拖拽 不以jQuery为基础 和视图模型同步刷新 和vue2的国度动画兼容 支持撤销操作 当需要完全控制时,可以抛出所有变化 可…...
 
双馈风力发电机-900V直流混合储能并网系统MATLAB仿真
MATLAB2016b主体模型:双馈感应风机模块、采用真实风速数据。混合储能模块、逆变器模块、转子过电流保护模块、整流器控制模块、逆变器控制模块。直流母线电压:有功、无功输出(此处忘记乘负一信号输出),所以是负的。蓄电…...
leader选举过程
启动electionTimer,进行leader选举。 一段时间没有leader和follower通信,就会超时,开始选举leader过程。有个超时时间,如果到了这个时间,就会触发一个回调函数。具体如下: private void handleElectionTimeout() {boo…...
 
建造者模式
介绍 Java中的建造者模式是一种创建型设计模式,它的主要目的是为了通过一系列简单的步骤构建复杂的对象,允许创建复杂对象的不同表示形式,同时隐藏构造细节.它能够逐步构建对象,即先创建基本对象,然后逐步添加更多属性或部件,直到最终构建出完整的对象. 该模式的主要思想是将…...
 
IO与NIO区别
一、概念 NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。 二、NIO和IO的主要区别 下表总结了Java I…...
 
无监督循环一致生成式对抗网络:PAN-Sharpening
Unsupervised Cycle-Consistent Generative Adversarial Networks for Pan Sharpening (基于无监督循环一致生成式对抗网络的全色锐化) 基于深度学习的全色锐化近年来受到了广泛的关注。现有方法大多属于监督学习框架,即对多光谱࿰…...
 
ArrayList源码分析(JDK17)
ArrayList类简介类层次结构构造无参构造有参构造添加元素add:添加/插入一个元素addAll:添加集合中的元素扩容mount与迭代器其他常见方法不常见方法不常见方法的源码和小介绍常见方法的源码和小介绍积累面试题ArrayList是什么?可以用来干嘛?Ar…...
数字IC/FPGA面试笔试准备(自用待填坑)
文章目录 前言常见的IC问题数字电路基础问题Verilog & SV跨时钟域信号处理类综合与时序分析类低功耗方法STA(静态时序分析)RTL设计(包含手撕代码)总线问题AXIAPBAHB体系结构的问题RISCV的问题一些笔试选择题前言 这是实验室师兄面试过程中整理的面试和笔试题目,目前只有题…...
 
基于多任务融合的圣女果采摘识别算法研究
基于多任务融合的圣女果采摘识别算法研究 1、简介 本文主要解决圣女果生产销售环节中,现有的流程是采摘成熟的圣女果,再对采摘下的果实进行单独的品质分级,不仅费时费力,而且多增加一个环节,也增加了对果实的二次伤害…...
 
又一个开源第一!飞桨联合百舸,Stable Diffusion推理速度遥遥领先
AIGC(AI Generated Content),即通过人工智能方法生成内容,是当前深度学习最热门的方向之一。其在绘画、写作等场景的应用也一直层出不穷,其中,AI绘画是大家关注和体验较多的方向。 Diffusion系列文生图模型可以实现AI绘画应用&…...
 
数据链路层及交换机工作原理
目录 一,帧格式 1.1 帧头类型字段的作用 1.2 MAC地址 1.3 MTU值 二,交换机工作原理 2.1 交换机的端口 2.2 端口状态 三,交换机基本工作模式及命令 3.1 交换机的工作模式: 3.2 命令 一,帧格式 其中类型是指&am…...
 
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
 
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
 
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
 
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
