【Linux】线程详解之线程控制

news2024/11/22 8:45:39

文章目录

  • POSIX线程库
  • 创建线程
  • 线程ID及进程地址空间布局
  • 线程等待
    • pthread_join
  • 线程终止
    • pthread_exit函数
    • pthread_cancel函数
  • 线程分离
  • 理解pthread库

POSIX线程库

POSIX线程(英语:POSIX Threads,常被缩写为Pthreads)是POSIX的线程标准,定义了创建和操纵线程的一套API。
实现POSIX 线程标准的库常被称作Pthreads,一般用于Unix-likePOSIX 系统,如Linux、Solaris。但是Microsoft Windows上的实现也存在,例如直接使用Windows API实现的第三方库pthreads-w32;而利用Windows的SFU/SUA子系统,则可以使用微软提供的一部分原生POSIX API。

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的要使用这些函数库,要通过引入头文<pthread.h>链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

在上篇文章提到,Linux的内核并没有真正实现线程的相关接口和相关的结构,而是使用进程的PCB模拟实现的,所以需要通过操作进程的相关接口来完成线程的控制,例如线程创建,线程等待,线程终止,线程分离等,所以最初的系统工程师,就将这些接口封装起来制作了pthread库,来在用户层实现线程的相关操作。

创建线程

在这里插入图片描述

功能:创建一个新的线程
原型
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,第二个参数可以设置线程的属性,我们通常传入NULL来设置默认,让编译器来处理,第三个参数是一个函数指针,是传给线程启动后要执行的函数,第四个参数是要传给启动函数的参数。
返回值:成功返回0,失败返回错误码

下边通过一段程序来验证一下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#define NUM 5

void* pthread_run(void* args)
{
   while(1){
    	int num = *(int*)args;
        printf("我是新线程[%d], 我的线程ID是: %lu\n", num,pthread_self());
        sleep(2);
        break;
   }
}
int main()
{
  pthread_t tid[NUM];
  for(int i=0; i<NUM; ++i)
  {
      pthread_create(&tid[i],NULL,pthread_run,(void*)&i);
      sleep(1);
  }

 while(1)
 {
     printf("我是主线程, 我的thread ID: %lu\n", pthread_self());

     printf("#########################begin########################\n");
     for(int i = 0; i < NUM; ++i)
     {
         printf("我创建的线程[%d]是: %lu\n", i, tid[i]);
     }
     printf("#########################end##########################\n");
     sleep(1);
 }
  return 0;
}

在这里插入图片描述
通过pthread_create接口,就可以创建了一个线程。

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

通过上边程序的验证,我们看到了每创建一个线程,就会有一个线程id,但是这个id代表什么呢?其实他代表一个地址,我们再通过16进制打印一下这个地址:
在这里插入图片描述
那么这个地址到底代表什么呢?我们一起来探究一下:
在进程运行起来之后,使用ps -aL指令可以查看轻量级进程,也就是LWP,而且我们发现第一个第一个线程的PID和LWP是相同的,这个就是主线程,而我们发现此处的轻量级进程ID和刚才获得的线程ID不一样,其实LWP才是内核中的线程ID,而刚才获得的只是用户级的ID。
在这里插入图片描述
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的,所以通过pthread_create接口获得的用户级线程是动态库中属于某一个线程结构体在进程地址空间中存放的地址。

对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

在这里插入图片描述

线程等待

和进程等待类似,如果不对线程进行等待,也可能会有僵尸进程的情况,已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,创建新的线程不会复用刚才退出线程的地址空间。
当一个线程退出时,也有三种情况:

代码跑完,结果不对。
代码跑完,结果对。
代码没有跑完,就异常终止了。

pthread_join

功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

第二个参数是一个二级指针,因为线程的启动函数的返回值是一个void*类型的变量,所以为了拿到这个返回值,我们使用二级指针来接收。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#define NUM 1

void* pthread_run(void* args)
{
   while(1)
   {
        int num = *(int*)args;
        printf("我是新线程[%d], 我的线程ID是: 0x%x\n", num,pthread_self());
        sleep(5);
        pthread_exit((void*)123);
   }
}
int main()
{
    pthread_t tid[NUM];
    for(int i=0; i<NUM; ++i)
    {
         pthread_create(&tid[i],NULL,pthread_run,(void*)&i);
         sleep(1);
    }
    void *status = NULL;
    int ret = 0;
    for(int i = 0; i < NUM; i++)
    {
        ret = pthread_join(tid[i], &status);
    }
    printf("ret: %d, status: %d\n", ret, (int)status);
    return 0;
}

在这里插入图片描述
此时的返回值为0,所以成功返回了,并且status退出码为123,就是我们之前设置的开启函数的退出码。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

线程终止

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

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

pthread_exit函数

功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr可以得到退出码信息,不关心就可以传入NULL,不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

使用pthread_exit函数可以终止自己,所以可以使用我们创建的线程来终止自己。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#define NUM 1

void* pthread_run(void* args)
{
   while(1)
   {
        int num = *(int*)args;
        printf("我是新线程[%d], 我的线程ID是: 0x%x\n", num,pthread_self());
        sleep(5);
        pthread_exit(NULL);
   }
}
int main()
{
    pthread_t tid[NUM];
    for(int i=0; i<NUM; ++i)
    {
         pthread_create(&tid[i],NULL,pthread_run,(void*)&i);
         sleep(1);
    }

    while(1)
   {
        printf("我是主线程, 我的thread ID: 0x%x\n", pthread_self());

     	printf("#########################begin########################\n");
     	for(int i = 0; i < NUM; ++i)
     	{
         	printf("我创建的线程[%d]是: 0x%x\n", i, tid[i]);
     	}
    	printf("#########################end########################\n");
     	sleep(1);
   }

   return 0;
}

pthread_cancel函数

功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#define NUM 1

void* pthread_run(void* args)
{
   while(1)
   {
       int num = *(int*)args;
       printf("我是新线程[%d], 我的线程ID是: 0x%x\n", num,pthread_self());
       sleep(2);
   }
}
int main()
{
   pthread_t tid[NUM];
   for(int i=0; i<NUM; ++i)
   {
    	pthread_create(&tid[i],NULL,pthread_run,(void*)&i);
    	sleep(1);
   }
    printf("wait sub thread....\n");
    sleep(1);
    
    printf("cancel sub thread ...\n");
    pthread_cancel(tid[0]);

    void *status = NULL;
    int ret = 0;
    for(int i = 0; i < NUM; i++)
    {
       ret = pthread_join(tid[i], &status);
    }
    printf("ret: %d, status: %d\n", ret, (int)status);
  return 0;
}

在这里插入图片描述

线程分离

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

函数:pthread_detach
detach:分开,脱离
头文件:#include <pthread.h>
函数原型:
int pthread_detach(pthread_t thread);
参数:
thread:被分离线程的ID
返回值:
线程分离成功返回0,失败返回错误码

可以是线程组中其他线程来分离某一线程

int pthread_detach(pthread_t thread);

也可以是自身线程来分离线程

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的,所以线程分离之后,就不能使用主线程来等待了

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#define NUM 1

void* pthread_run(void* args)
{
       pthread_detach(pthread_self());
       while(1){
       int num = *(int*)args;
       printf("我是新线程[%d], 我的线程ID是: 0x%x\n", num,pthread_self());
       sleep(2);
  }
}
int main()
{
  pthread_t tid[NUM];
  for(int i=0; i<NUM; ++i)
  {
    pthread_create(&tid[i],NULL,pthread_run,(void*)&i);
    sleep(1);
  }
    void *status = NULL;
    int ret = 0;
    for(int i = 0; i < NUM; i++)
    {
       ret = pthread_join(tid[i], &status);
    }
    printf("ret: %d, status: %d\n", ret, (int)status);
  return 0;
}

在这里插入图片描述
此时等待的返回值不为0,所以此时等待失败。

理解pthread库

任何语言,要在Linux上使用多线程,底层必须封装pthread库,其实pthread库是在用户级别的,而库中对应着每个线程的相关属性以及他们的栈,用户级得到的id是在对应某一线程的动态库在虚拟地址空间中的地址,而内核中的TWP才是内核中该线程的id,这个id一定存储在该线程动态库的结构体中,他是线程的一个属性,而线程的动态库被加载到进程地址空间的共享区中。
在这里插入图片描述

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

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

相关文章

若依源码解析:DataScopeAspect实现数据范围的控制

文章目录 源代码使用场景界面操作SysDeptServiceImplSysUserServiceImplSysUserMapperDataScope定义 代码解析Aspect和Component不同的数据权限类型Before通知处理数据范围的方法 源代码 Aspect Component public class DataScopeAspect {/*** 全部数据权限*/public static fi…

Python潮流周刊#2:Rust让Python再次伟大

△点击上方“Python猫”关注 &#xff0c;回复“1”领取电子书 这里记录每周值得分享的 Python 及通用技术内容&#xff0c;部分为英文&#xff0c;已在小标题注明。&#xff08;本期标题取自其中一则分享&#xff0c;不代表全部内容都是该主题&#xff0c;特此声明。&#xff…

【Linux Network】I/O多路转接之select

目录 1. 初识select 1.1 select函数原型 1.2 理解select执行过程 1.3 socket就绪条件 1.4 select的特点 1.5 select优缺点 2. 基于select的多人聊天程序 server源代码&#xff1a; client的登录&#xff1a; 结果演示&#xff1a; Linux Network&#x1f337; 1. 初识select 系…

C++初阶--C++入门之基础学习

0.前言 C是一门非常好的编程语言&#xff0c;但可能在学习C的过程中会遇到很多困难。人们常说 “一个人走得很快&#xff0c;一群人会走的更远”&#xff0c; 所以就让我们一起攻坚克难&#xff0c;一起征服C吧&#xff01;从本章开始&#xff0c;我们将开始C的基础学习&#x…

Linux简介及基础操作

1.Linux的作用&#xff1a; 商业服务器基本都是linux的、开源软件都先支持linux、大数据分析&#xff0c;机器学习首选linux、整个互联网地基基本由linux支撑起来。如&#xff1a; 生活中的手机是基于linux二次开发的&#xff0c;还有路由器也是基于linux开发的。 2.Linux是什…

acwing提高--多源BFS+最小步数模型+双端队列广搜

多源BFS 1.矩阵距离 题目https://www.acwing.com/problem/content/description/175/ #include<bits/stdc.h> using namespace std; #define x first #define y second typedef pair<int,int> PII; const int N1010; char g[N][N]; int dist[N][N]; PII q[N*N];…

【轻量化网络系列(2)】MobileNetV2论文超详细解读(翻译 +学习笔记+代码实现)

前言 上一篇我们介绍了MobileNetV1&#xff0c;主要是将普通Conv转换为dw和pw&#xff0c;但是在dw中训练出来可能会很多0&#xff0c;也就是depthwise部分得到卷积核会废掉&#xff0c;即卷积核参数大部分为0&#xff0c;因为权重数量可能过少&#xff0c;再加上Relu激活函数…

稳定币是个好生意

* * * 原创&#xff1a;刘教链 * * * 本月早些时候&#xff0c;市值第一的稳定币发行商Tether公布了其一季度的储备和盈利数据[1]。不能说是亮眼&#xff0c;只能说是非常亮眼。就看几个亮点吧&#xff1a; 1. 一季度净利润14.8亿美元&#xff0c;是2022年四季度的两倍多&…

关于Java中的抽象类注意事项

文章目录 &#x1f3c6;文章导读&#x1f342;抽象类的定义&#x1f342;抽象类的特性&#x1f342;总结&#xff1a;面试题普通类和抽象类有哪些区别&#xff1f;抽象类能使用final继承吗&#xff1f; &#x1f3c6;文章导读 在本篇文章中&#xff0c;对抽象类进行了一个详细的…

c++学习——c与c++const修饰的变量的区别

c语言下const修饰的变量 1、c语言下const修饰的变量都有空间 2. c语言的const修饰的全局变量具有外部链接属性 07 const修饰的变量.c #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h>const int a 10;//常…

1. Linux环境搭建及问题解决方案

本文介绍了Linux环境搭建的过程以及遇到的问题和解决方案&#xff0c;并且介绍了常用的Linux命令. 一、Linux环境搭建 整体所需的环节 安装VMware安装Linux &#xff08;这边我选的是Server版本&#xff09;安装配置Samba&#xff08;Samba是一种Linux和Windows之间进行文件共…

二层环路详解:交换机环路产生的过程和原因

前言&#xff1a; 在了解环路之前得先了解交换机的工作原理&#xff0c;当然交换机的基本工作原理其实非常简单&#xff0c;只有“单播转发与泛洪转发”、“交换机MAC地址表”这两个&#xff01;其他的如vlan&#xff0c;生成树等也是在此基础上增加的&#xff0c;弥补交换机基…

初始Linux的基本操作

上篇博客中&#xff0c;我介绍了关于Linux的相关概念&#xff0c;让我们初步的了解到Linux的重要性&#xff0c;在这篇博客中我会再讲一些Linux操作系统的理解。 一.操作系统 我们知道Linux是一个操作系统&#xff0c;而操作系统操作系统(英语&#xff1a;Operating System&…

[深度好文]10张图带你轻松理解关系型数据库系统的工作原理

[深度好文]10张图带你轻松理解关系型数据库系统的工作原理 原文(欢迎关注)&#xff1a;https://mp.weixin.qq.com/s/CNCfWRpv8QlICGvZkLG4Jw 尽管数据库在我们应用程序中扮演着储存几乎所有状态的关键角色&#xff0c;但人们对其运行原理的了解通常仅停留在较为浅显的层面&…

跟我一起使用 compose 做一个跨平台的黑白棋游戏(4)移植到compose-jb实现跨平台

前言 在上一篇文章中&#xff0c;我们已经实现了游戏的所有界面和逻辑代码&#xff0c;并且在 Android 上已经可以正常运行。 这篇文章我们将讲解如何将其从使用 jetpack compose 修改为使用 compose-jb 从而实现跨平台。 老规矩&#xff0c;先看效果图&#xff1a; 可以看到…

063:cesium设置带边界线材质(material-7)

第063个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置带边界折线材质,请参考源代码,了解PolylineOutlineMaterialProperty的应用。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共89行)相关API参考…

Python-matplotlib中的pie(饼)图

Python-matplotlib中的pie&#xff08;饼&#xff09;图 %matplotlib inline import matplotlib.pyplot as pltm 51212 f 40742 m_perc m/(mf) f_perc f/(mf)colors [navy,lightcoral] labels ["Male","Female"]plt.figure(figsize(8,8)) paches,te…

为什么不胜任的人,反而获得晋升?

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 也许你有过这样的经历&#xff0c;自己勤勤恳恳地干活&#xff0c;每个月却只拿着微薄的薪水&#xff0c;有些人明明无法胜任工作&#xff0c;却像坐了火箭一样飞速晋升。这种现象在现实生活中无处不在…

3699元还配同价位最好屏幕!Redmi Book 14评测:几乎完美的“水桶”轻薄本

一、前言&#xff1a;4K价位最好屏幕 不久前&#xff0c;有网友让我推荐一台4000元价位的轻薄本&#xff0c;笔者直接选了一台搭载i5-13500H处理器且价格仅售4299元的某一线品牌产品。 但是&#xff0c;事后才发现不对&#xff0c;因为这款极具性价比的笔记本竟然用了45%NTSC色…

MIT6.824 lecture5上课笔记(涉及到Lab2A)- Go threads and raft

总结&#xff1a;本节课讲解了一些会在lab2中使用到的go的多线程技巧&#xff0c;会给一些简单的demo&#xff0c;lab2中可能会借鉴这些demo。 详细的Lab2 raft算法实现源码&#xff0c;请参考我的个人仓库&#xff08;记得点颗星星&#xff09;, 配合readme食用更佳。 MIT6.…