当前位置: 首页 > news >正文

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线程控制

本篇我将学习如何使用多线程。要使用多线程&#xff0c;因为Linux没有给一般用户直接提供操作线程的接口&#xff0c;我们使用的接口&#xff0c;都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此&#xff0c;需要引入-lpthread&#xff0c;即连接原生…...

【LeetCode】剑指 Offer(20)

目录 题目&#xff1a;剑指 Offer 38. 字符串的排列 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 38. 字符串的…...

FutureTask中的outcome字段是如何保证可见性的?

最近在阅读FutureTask的源码是发现了一个问题那就是源码中封装结果的字段并没有使用volatile修饰&#xff0c;源码如下&#xff1a;public class FutureTask<V> implements RunnableFuture<V> {/*** 状态变化路径* Possible state transitions:* NEW -> COMPLET…...

直播回顾 | 聚焦科技自立自强,Bonree ONE 助力国产办公自动化平稳替代

3月5日&#xff0c;两会发布《政府工作报告》&#xff0c;强调科技政策要聚焦自立自强。 统计显示&#xff0c;2022年金融信创项目数同比增长300%&#xff0c;金融领域信创建设当前已进入发展爆发期&#xff0c;由国有大型银行逐渐向中小型银行、非银金融机构不断扩展。信创云…...

深入理解Linux进程

进程参数和环境变量的意义一般情况下&#xff0c;子进程的创建是为了解决某个问题。那么解决问题什么问题呢&#xff1f;这个就需要进程参数和环境变量来进行决定的。子进程解决问题需要父进程的“数据输入”(进程参数 & 环境变量)设计原则&#xff1a;3.1 子进程启动的时候…...

Vue3之组件间的双向绑定

何为组件间双向绑定 我们都知道当父组件改变了某个值后&#xff0c;如果这个值传给了子组件&#xff0c;那么子组件也会自动跟着改变&#xff0c;但是这是单向的&#xff0c;使用v-bind的方式&#xff0c;即子组件可以使用父组件的值&#xff0c;但是不能改变这个值。组件间的…...

Java语法基础(一)

目录 代码注释方法 编码规范 基本数据类型及取值范围 变量和常量的声明与赋值 变量 常量 标识符 基本数据类型的使用 整数类型的使用 浮点类型的使用 布尔类型的使用 字符类型的使用 代码注释方法 单行注释&#xff1a;使用“//”进行单行注释多行注释&#xff1a;使…...

优思学院|零质量控制是什么概念?

零质量控制&#xff08;Zero Quality Control&#xff09;是指一个理想的系统&#xff0c;可以生产没有任何缺陷的产品&#xff0c;因此不需要频繁的检查&#xff0c;从而节省时间和金钱。那些追求过程优化并致力于持续过程改进的组织将零质量控制&#xff08;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组件&#xff0c;用以实现拖拽功能。 特性 支持触摸设备 支持拖拽和选择文本 支持智能滚动 支持不同列表之间的拖拽 不以jQuery为基础 和视图模型同步刷新 和vue2的国度动画兼容 支持撤销操作 当需要完全控制时&#xff0c;可以抛出所有变化 可…...

双馈风力发电机-900V直流混合储能并网系统MATLAB仿真

MATLAB2016b主体模型&#xff1a;双馈感应风机模块、采用真实风速数据。混合储能模块、逆变器模块、转子过电流保护模块、整流器控制模块、逆变器控制模块。直流母线电压&#xff1a;有功、无功输出&#xff08;此处忘记乘负一信号输出&#xff09;&#xff0c;所以是负的。蓄电…...

leader选举过程

启动electionTimer&#xff0c;进行leader选举。 一段时间没有leader和follower通信&#xff0c;就会超时&#xff0c;开始选举leader过程。有个超时时间&#xff0c;如果到了这个时间&#xff0c;就会触发一个回调函数。具体如下: 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 &#xff08;基于无监督循环一致生成式对抗网络的全色锐化&#xff09; 基于深度学习的全色锐化近年来受到了广泛的关注。现有方法大多属于监督学习框架&#xff0c;即对多光谱&#xff0…...

ArrayList源码分析(JDK17)

ArrayList类简介类层次结构构造无参构造有参构造添加元素add&#xff1a;添加/插入一个元素addAll:添加集合中的元素扩容mount与迭代器其他常见方法不常见方法不常见方法的源码和小介绍常见方法的源码和小介绍积累面试题ArrayList是什么&#xff1f;可以用来干嘛&#xff1f;Ar…...

数字IC/FPGA面试笔试准备(自用待填坑)

文章目录 前言常见的IC问题数字电路基础问题Verilog & SV跨时钟域信号处理类综合与时序分析类低功耗方法STA(静态时序分析)RTL设计(包含手撕代码)总线问题AXIAPBAHB体系结构的问题RISCV的问题一些笔试选择题前言 这是实验室师兄面试过程中整理的面试和笔试题目,目前只有题…...

基于多任务融合的圣女果采摘识别算法研究

基于多任务融合的圣女果采摘识别算法研究 1、简介 本文主要解决圣女果生产销售环节中&#xff0c;现有的流程是采摘成熟的圣女果&#xff0c;再对采摘下的果实进行单独的品质分级&#xff0c;不仅费时费力&#xff0c;而且多增加一个环节&#xff0c;也增加了对果实的二次伤害…...

又一个开源第一!飞桨联合百舸,Stable Diffusion推理速度遥遥领先

AIGC(AI Generated Content)&#xff0c;即通过人工智能方法生成内容&#xff0c;是当前深度学习最热门的方向之一。其在绘画、写作等场景的应用也一直层出不穷&#xff0c;其中&#xff0c;AI绘画是大家关注和体验较多的方向。 Diffusion系列文生图模型可以实现AI绘画应用&…...

数据链路层及交换机工作原理

目录 一&#xff0c;帧格式 1.1 帧头类型字段的作用 1.2 MAC地址 1.3 MTU值 二&#xff0c;交换机工作原理 2.1 交换机的端口 2.2 端口状态 三&#xff0c;交换机基本工作模式及命令 3.1 交换机的工作模式&#xff1a; 3.2 命令 一&#xff0c;帧格式 其中类型是指&am…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

SpringAI实战:ChatModel智能对话全解

一、引言&#xff1a;Spring AI 与 Chat Model 的核心价值 &#x1f680; 在 Java 生态中集成大模型能力&#xff0c;Spring AI 提供了高效的解决方案 &#x1f916;。其中 Chat Model 作为核心交互组件&#xff0c;通过标准化接口简化了与大语言模型&#xff08;LLM&#xff0…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋&#xff0c;无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话&#xff0c;配置.bahs_profile后也能解决上下翻页这些&#xff0c;但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...

02.运算符

目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&&#xff1a;逻辑与 ||&#xff1a;逻辑或 &#xff01;&#xff1a;逻辑非 短路求值 位运算符 按位与&&#xff1a; 按位或 | 按位取反~ …...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

ubuntu中安装conda的后遗症

缘由: 在编译rk3588的sdk时&#xff0c;遇到编译buildroot失败&#xff0c;提示如下&#xff1a; 提示缺失expect&#xff0c;但是实测相关工具是在的&#xff0c;如下显示&#xff1a; 然后查找借助各个ai工具&#xff0c;重新安装相关的工具&#xff0c;依然无解。 解决&am…...