操作系统~Linux~线程控制,POSIX线程库的使用

news2025/1/18 11:54:03

1.POSIX线程库

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

2.创建线程-pthread_create()

功能:创建一个新的线程

原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *

(*start_routine)(void*), void *arg);

参数

  1. thread:是一个输出型参数,返回线程ID,常用来保存线程id(无符号长整形)
  2. attr:设置线程的属性,attr为NULL表示使用默认属性,一般都是NULL
  3. start_routine:是个函数地址,线程启动后要执行的函数
  4. arg:传给线程启动函数的参数

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

pthread_self函数

pthread_self()函数可以返回当前线程的id,这是一个地址,所以我们用十六进制打印这个地址

代码举例

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdlib>
#include <cstring>
using namespace std;

void *thread_run(void* args)
{
    char *s = (char*)args; //首先要将void*的参数转成对应的类型
    while(1)
    {
        cout << "Get the param :" << s << endl;
        printf("new thread id: 0x%x\n", pthread_self());
        sleep(2);
    }
}

int main()
{
    char *s = new char[6];
    strcpy(s,"zebra");
    pthread_t tid;
    pthread_create(&tid, NULL, thread_run, s);


    while(1){
        printf("main thread id: 0x%x\n", pthread_self());
        sleep(2);
    }
}

结果分析

        可以看到,主线程的线程id和新线程的线程id是不同的,而且是地址,同时我们可以发现参数zebra成功传递到了thread_run函数中。


3.线程等待-pthread_join(主线程等待新线程)

        一般而言,线程也是需要被等待的,如果不等待,可能会导致类似于"僵尸进程"的问题

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。

功能:等待线程结束,调用该函数的线程将挂起等待,直到id为thread的线程终止(也就是主线程阻塞式等待id为thread的进程终止)

原型

int pthread_join(pthread_t thread, void **value_ptr);

参数

  1. thread:线程ID
  2. value_ptr:是一个输出型参数,返回线程执行完函数后的返回值(这个函数的返回值也就是这个线程的返回值),我们创建函数的时候,传入的线程执行函数返回值是void*的,所以这里定义一个指向void*类型变量的指针。

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

e.g.:主线程等待其他线程执行完毕后才能继续执行(只能用这种for循环的方式,一个个等):

注:如果在c++中,虽然可以把int转换成void*类型,但是无法将void*转回int,会报错,因为你是64位机器,void*是8字节,int是4字节,会有精度丢失,c++不允许这种精度丢失,c语言允许。

所以如果在c++中,如果我们要使用void*类型的8个字节存储实际内容(void*类型本身就可以充当容器),我们需要使用long,将void*转成long类型,然后再转回去,不会有精度丢失,因为long在64位下是8字节

代码举例(使用void*存储参数的值)

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
const int NUM = 10;
void *run(void *param)
{
    // int data = *(int*)param;  //如果在c++中,虽然可以把int转换成void*类型,但是无法将void*转回int,会报错,因为你是64位机器,void*是8字节,int是4字节,会有精度丢失,c++不允许这种精度丢失,c语言允许
    // 所以如果在c++中,我们需要使用long,将void*转成long类型,然后再转回去,不会有精度丢失,因为long在64位下是8字节
    long data = (long)param;
    printf("current thread is %d\n",data);
    return (void*)data;
}

int main()
{
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
        pthread_create(&tid[i],NULL,run,(void*)i);
        usleep(100000);
    }
    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, (long)status);
    }

    return 0;
}

结果分析

从结果中可以看出

        1.我们将参数i转换成void*类型,传到run方法内部,然后强转成long后,可以成功获取到参数i的值。

        2.pthread_join函数可以等待线程,并通过一个输出型参数获取函数的返回值,也就是线程的返回值。

注意:我们创建线程执行完对应的函数后,有三种情况:

1.代码跑完结果对

2.代码跑完结果不对

3.代码异常了

前两种pthread_join可以通过输出型参数status来判断代码结果是否正确,并作出处理

第三种代码异常,pthread_join不需要处理,处理异常是进程来做的。


 4.线程终止的方案

1.函数中return

        a. main函数退出return的时候代表主线程and进程退出

        b.其他线程函数return,只代表当前线程退出

2.新线程通过pthread_exit终止自己( vs exit是终止进程,不要在其他线程中调用,如果你就想终止一个线程的话! !)

3.一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

(1).pthread_exit函数

功能:终止调用该函数的线程

原型

void pthread_exit(void *value_ptr);

参数

value_ptr:value_ptr不要指向一个局部变量,一个输入型参数。这个参数会作为线程退出后的返回值

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

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


(2).pthread_cancel函数

功能:取消一个执行中的线程,取消线程ID为传入参数的线程

原型

int pthread_cancel(pthread_t thread);

参数

thread:线程ID(可以在创建的时候用变量把线程id保存下来,在这里就可以拿来用

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

注意事项:

取消线程以后,线程退出的返回值是-1(对应的是PTHREAD_CANCELED宏)


5.线程分离-pthread_detach函数

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

线程分离,分离之后的线程不需要被join(不能被join)运行完毕之后,会自动释放Z状态的pcb

int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());


6.pthread中的线程id与Linux内核中的轻量级线程id的区别

        我们看到的库里面的线程id是pthread库的线程id,不是Linux内核中的LWP(light weight process),pthread库的线程id是一个内存地址,而Linux内核中的LWP是一个数值。

1.我们使用多线程需要用到pthread库,这是一个采用动态链接的共享库,加载到内存最后都放在栈和堆之间的共享区(共享内存,动态库都是放在这里的),内存中只有一份。(通过线程自己的页表映射到同一个物理空间)

        每个线程都要有运行时的临时数据,每个线程都要有自己的私有栈结构,描述线程的用户级控制块。

2.每一个新线程创建的时候,都会在pthread库里创建一个pthread结构体(每个线程都使用虚拟地址空间-mm_struct,注意task_struct包括mm_struct,每个线程有一个task_struct也就是PCB,其内部的mm_struct里面的共享区里面存放有所有线程的pthread结构体),用来保存线程的相关信息(线程的用户级数据,线程的私有栈),有100个线程,那在共享区内部的pthread库里面就会有100个用来保存pthread信息的结构体。

        在这个pthread库中,如何快速找到对应的pthread结构体呢,只要拿到线程id就行,线程id就是pthread结构体的地址,只要拿到这个地址,我们就可以很轻松地找到pthread,获取线程运行时的用户级数据。

3.用户层调用pthread_create创建一个线程的时候,会在共享区内部创建一个pthread结构体,保存线程的栈,用户级数据(临时数据)等信息;返回给用户的线程id就是这个pthread结构体的地址。同时,在Linux内核中,也要为线程创建对应的pcb(CPU调度由pcb说了算)。其中,也要创建一个与当前线程id对应的lwp(在pthread结构体里面保存lwp,内核的pcb里面保存线程id,也就是pthread结构体的地址)。

用户级线程1:1式的和Linux内核中的轻量级线程对应,这就是Linux实现线程的方式。

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

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

相关文章

在HTML页面中引用Markdown编辑器(Editor.md)

目录 1、下载Ediotor.md 2、引入Ediotor.md 3、确定Ediotor.md在哪里显示 最近写博客项目&#xff0c;用到了Markdown编辑器&#xff0c;这里介绍一款国内好用的Markdown编辑器&#xff1a;Editor.md&#xff0c;下面介绍一下该编辑器以及如果在页面中引用。 1、下载Edioto…

【TypeScript】类型兼容性与相关类型讲解

目录 类型兼容性 对象类型兼容性 接口类型兼容性 函数类型兼容性 索引签名类型 映射类型 索引查询类型 交叉类型 类型兼容性 在TS中&#xff0c;类型采用的是结构化类型系统&#xff0c;也叫做 duck typing&#xff08;鸭子类型&#xff09;&#xff0c;类型检查关注的…

电脑c盘备份怎么操作?备份C盘的6个步骤

电脑出现问题&#xff0c;想要修复又不知该怎么操作。可能你会想要重装电脑的系统&#xff0c;但是在操作之前&#xff0c;一定要对电脑重要的数据做好备份。尤其是电脑C盘里面存储着我们很多重要的数据&#xff0c;电脑c盘备份怎么进行&#xff1f;跟着下面6个操作步骤进行&am…

<Linux线程池、线程安全(单例模式、STL、智能指针)、读者写者问题及线程扩展与总结>——《Linux》

目录 1.线程池 1.1 线程池: 1.2 线程池的应用场景&#xff1a; 1.3 线程池的种类&#xff1a; 1.4 线程池示例&#xff1a; 1.5 线程池编程模拟实现&#xff1a; 2. 线程安全的单例模式 2.1 什么是单例模式 2.2 什么是设计模式 2.3 单例模式的特点 2.3.1 饿汉实现方…

驱动相关基础

1.程序分类 1.1 裸机程序&#xff1a;直接运行在对应硬件的的程序 1.2 应用程序&#xff1a;只能运行在对应操作系统上的程序 2. 计算机系统的层次结构 2.1 无操作系统的简单的两层结构 2.2 有操作系统的四层层次结构 3. 操作系统 狭义&#xff1a;给应用程序提供运行环…

Python图像处理【7】采样、卷积与离散傅里叶变换

采样、卷积与离散傅里叶变换0. 前言1. 图像傅里叶变换1.1 傅里叶变换基础1.2 傅里叶变换应用1.3 逆傅里叶变换应用2. 利用采样改变图像分辨率2.1 上采样2.2 下采样小结系列链接0. 前言 采样 (Sampling) 是用于选择/丢弃图像像素的空间操作&#xff0c;通常用于增加/减小图像大…

(byte)1658385462>>16=-40,怎么算的?

正文 在 Github 项目mongo-java-driver有一个类ObjectId.java&#xff0c;它的作用是生成唯一 id 的&#xff0c;它的核心实现是下面这样一段代码 [1]&#xff1a; public void putToByteBuffer(final ByteBuffer buffer) {notNull("buffer", buffer);isTrueArgume…

【数据结构Java版】树与二叉树的相关知识全解

目录 一、树型结构 &#xff08;1&#xff09;树的定义 &#xff08;2&#xff09;树的基本术语 &#xff08;3&#xff09;树的存储结构 二、二叉树 &#xff08;1&#xff09;二叉树的定义 &#xff08;2&#xff09;两种特殊二叉树 1.满二叉树 2.完全二叉树 &…

CSS中你可能不知道的Selectors特性

CSS中你可能不知道的Selectors特性本文作者为奇舞团前端开发工程师引言最近看了2022年全球CSS调查报告&#xff0c;发现了一些不常见的伪类和伪元素。伪类:has()html<div><h1>H1</h1><h2>H2</h2><p>h1{margin: 0 0 0.25rem 0}</p> &…

设置访问SSH为密钥访问

1.制作密钥对 ssh-keygen输入会问两个问题 设置公私钥名称&#xff08;可以留白&#xff0c;直接回车&#xff09;设置公私钥密码&#xff08;可以留白&#xff0c;直接回车&#xff09; 第一次输入第二次确认 留空确认的话&#xff0c;生成公私钥。共有两个文件 # 私钥 id…

Rxjava源码分析实践(三)【RxJava基本原理分析之订阅流】

本节,我们从Rxjava使用代码入手,去结合自己已有的知识体系,加查阅部分源码验证的方式,来一起探索一下Rxjava实现的基本原理。 为了本文原理分析环节,可以被更多的人理解、学习,所以小编从初学者的角度,从使用入手,一点点的分析了其中的源码细节、思想,建议大家随着本文…

NCMMSC论文介绍 | 探索语音自监督模型的高效融合算法

本文介绍了清华大学语音与音频技术实验室&#xff08;SATLab&#xff09;与上海交通大学跨媒体语言智能实验室&#xff08;X-LANCE&#xff09;合作的NCMMSC录用论文&#xff1a;Exploring Effective Fusion Algorithms for Speech Based Self-Supervised Learning Models。该论…

动态列合并更新

【问题】 I have one query, would be great if anyone can help me out on this. In SQL, I have two tables with same column names. Want to query if there is any difference in the column values and if yes will update the values(in the first table) else if the…

【工具类】后台Mock工具类

目录一、介绍二、使用方法1. Controller层定义接口2. 编写json文件3. 开启AOP4. 调用接口验证三、源码一、介绍 Controller层定义完接口后&#xff0c;不需要写业务逻辑。编写Json文件&#xff0c;调用接口时返回json文件的数据。 优点&#xff1a; 设计阶段即可定义好接口&…

Centos 图形化yum管理工具 - yum Extender

文章目录背景安装开启yum-GUI工具 - yumexyum list installed列出软件包的依赖yum cleam背景 作为一个yum工程师&#xff0c;长期备受yum 命令的煎熬。 难道yum就乜有一个GUI管理界面吗&#xff1f; yum Extender (简称 yumex ) , 是 yum 的图形化操作界面。可以通过 yumex 方…

ActiveMQ高级特性和大厂面试常考重点

目录 一、引入消息队列之后该如何保证其高可用性 二、异步投递Async Sends 三、延迟投递和定时投递 四、ActiveMQ消费重试机制 五、死信队列 六、如何保证消息不被重复消费呢?幂等性问题你谈谈 一、引入消息队列之后该如何保证其高可用性 ActiveMQ集群模式_zoeil的博客-…

【机器学习】KNN 算法介绍

文章目录一、KNN 简介二、KNN 核心思想实例分析&#xff1a;K 值的影响三、KNN 的关键1. 距离计算1. 闵可夫斯基距离2. 曼哈顿距离3. 欧氏距离4. 切比雪夫距离5. 余弦距离总结2. K值选择四、KNN 的改进&#xff1a;KDTree五、KNN 回归算法参考链接一、KNN 简介 KNN 算法&#…

想在微信上使用chatGPT?小程序?公众号?企业微信,最终还是选择了企业微信版本的chatgpt

chatgpt的接口现在都可以正常用了&#xff0c;但是怎么把这个功能放在手机上随用随开呢&#xff1f;微信个人聊天版本小程序版本公众号版本企业微信版本逻辑实现方式微信个人聊天版本 网上很多微信机器人版本的&#xff0c;但是原理是网页版微信&#xff0c;很多账号都不能登陆…

golang指针

指针 区别于C/C中的指针&#xff0c;Go语言中的指针不能进行偏移和运算&#xff0c;是安全指针。 要搞明白Go语言中的指针需要先知道3个概念&#xff1a;指针地址、指针类型和指针取值。 1.1. Go语言中的指针 Go语言中的函数传参都是值拷贝&#xff0c;当我们想要修改某个变…

Linux中如何理解线程?线程ID到底是什么?

朋友们好&#xff0c;这里简要介绍了进程和线程的区别以及对LINUX中线程ID的理解&#xff0c;本人目前理解尚浅&#xff0c;若文中有表述不当的地方还望理解并指正&#xff0c;谢谢大家&#xff01; 文章目录一&#xff1a;进程和线程二&#xff1a;线程ID和进程地址空间布局一…