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

news2024/12/24 10:01:24

目录

前言

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.coloradmin.cn/o/1615147.html

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

相关文章

http 3.0 有哪些新特性

HTTP/3 是超文本传输协议&#xff08;HTTP&#xff09;的最新主要版本&#xff0c;其显著特点是放弃了传统的TCP作为传输层协议&#xff0c;转而采用基于UDP的QUIC&#xff08;Quick UDP Internet Connections&#xff09;协议。以下是HTTP/3利用QUIC实现高性能传输的关键特性&…

街道社区信息宣传工作做的好这个投稿方法不能少

作为一名刚刚接手街道社区信息宣传工作的新人,伊始对于如何有效地向各大媒体平台投稿我可谓是一头雾水。那时的日子充满了曲折与挑战,每一步都似乎布满了荆棘。为了让更多居民了解社区的工作动态和服务亮点,我怀揣着满腔热情,着手撰写一篇篇生动详实的新闻稿件。然而,投稿的过程…

mac上VMware fusion net模式无法正常使用的问题

更新时间&#xff1a;2024年04月22日21:39:04 1. 问题 环境&#xff1a; intel芯片的macbook pro VMware fusion 13.5.1 无法将“Ethernet0”连接到虚拟网络“/dev/vmnet8”。在这里显示这个之后&#xff0c;应该是vmnet8的网段发生了冲突&#xff0c;所以导致无法正常使用…

HTML 如何实现一个带间隙的圆环

实际效果&#xff1a; ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/5e634cedded9424d96fbe6d46f34f61a.png#pic_center 代码实现&#xff1a; HTML部分&#xff1a; <svg width"500" height"500" viewBox"0 0 100 100">&…

【机器学习-17】数据变换---小波变换特征提取及应用案列介绍

引言 在机器学习领域&#xff0c;数据变换是一种常见且重要的预处理步骤。通过对原始数据进行变换&#xff0c;我们可以提取出更有意义的特征&#xff0c;提高模型的性能。在众多数据变换方法中&#xff0c;小波变换是一种非常有效的方法&#xff0c;尤其适用于处理非平稳信号和…

科学中的概率大师:Avi Wigderson 教授的图灵奖探索

文章目录 前言一、图灵奖的新星二、揭秘计算随机性三、Wigderson 的研究突破四、学术与产业的共鸣五、荣誉与责任总结 前言 在计算机科学的辽阔星空中&#xff0c;随机性与伪随机性犹如迷人的双子星座&#xff0c;挑战着我们的智力边界。Avi Wigderson&#xff0c;普林斯顿大学…

创建虚拟环境(记录一下)

conda create -n name python3.8版本高于11.7&#xff1a; pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117 --force-reinstall --user 检验是否为true import torch print(torch.cuda.is_available()) stable diff…

(十六)call、apply、bind介绍、区别和实现

函数中的this指向&#xff1a; 函数中的this指向是在函数被调用的时候确定的&#xff0c;也就是执行上下文被创建时确定的。在一个执行上下文中&#xff0c;this由调用者提供&#xff0c;由调用函数的方式来决定。 类数组对象arguments&#xff1a; arguments只在函数&#…

消息队列选型(RabbitMq、RocketMq、Kafaka)

文章目录 前言RabbitMq优点缺点 RocketMq优点缺点 Kafaka优点缺点 总结 前言 当引入消息队列时&#xff0c;常见的选择包括ActiveMQ、Kafka、RabbitMQ和RocketMQ。然而&#xff0c;近年来&#xff0c;ActiveMQ的活跃度已经下降&#xff0c;很多公司已经不再使用这款消息队列中…

DBeaver导入sql文件

DBeaver导入sql文件 下载数据库 数据库下载地址&#xff1a; https://www.begtut.com/mysql/mysql-sample-database.html数据库导入 获取sql文件中创建的数据库的名称&#xff0c;创建一个同名的数据库。 输入数据库的名称&#xff0c;设置字符集和排序规则 数据库创建完…

基于Springboot的人职匹配推荐系统

基于SpringbootVue的人职匹配推荐系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 企业信息 岗位信息 新闻资讯 后台管理 用户管理 企业信息管理 岗位信…

【Linux高性能服务器编程】——高性能服务器框架

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的Linux高性能服务器编程系列之高性能服务器框架介绍&#xff0c;在这篇文章中&#xff0c;你将会学习到高效的创建自己的高性能服务器&#xff0c;并且我会给出源码进行剖析&#xff0c;以及手绘UML图来帮助大家来理解&…

LeetCode:组合求和III之回溯法

题目 题目链接 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a;只使用数字1到9 每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。题目图解 ** ** cpp代码 class …

大模型改变了NLP的游戏规则了吗

NLP已经死了吗&#xff1f; 自从 ChatGPT 横空出世以来&#xff0c;自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09; 研究领域就出现了一种消极的声音&#xff0c;认为大模型技术导致 NLP “死了”。在某乎上就有一条热门问答&#xff0c;大…

03-为啥大模型LLM还没能完全替代你?

1 不具备记忆能力的 它是零状态的&#xff0c;我们平常在使用一些大模型产品&#xff0c;尤其在使用他们的API的时候&#xff0c;我们会发现那你和它对话&#xff0c;尤其是多轮对话的时候&#xff0c;经过一些轮次后&#xff0c;这些记忆就消失了&#xff0c;因为它也记不住那…

Python 开发实现登陆和注册模块

Python 开发实现登陆和注册模块 一、案例介绍 本例设计一个用户登录和注册模块&#xff0c;使用Tkinter框架构建界面&#xff0c;主要用到画布、文本框、按钮等组件。涉及知识点&#xff1a;Python Tkinter界面编程、pickle数据存储。本例实现了基本的用户登录和注册互动界面…

纹理合成在AI去衣技术中的关键作用

随着人工智能技术的飞速发展&#xff0c;图像处理和计算机视觉领域取得了显著的进步。其中&#xff0c;AI去衣技术作为图像处理的一个分支&#xff0c;近年来引起了广泛关注。在AI去衣技术中&#xff0c;纹理合成发挥着至关重要的作用&#xff0c;它不仅能够保证图像的真实性&a…

YOLO算法改进Backbone系列之MogaNet:

卷积神经网络&#xff08;ConvNets&#xff09;一直是计算机视觉的首选方法。受灵长类视觉系统的启发&#xff0c;卷积层可以对具有区域密集连接和平移等方差约束的观测图像的邻域相关性进行编码。通过交错分层&#xff0c;ConvNets获得了被动增加的感受野&#xff0c;并善于识…

掼蛋比赛中的违规及处罚

一、越序违规及处罚 1、越序抓牌&#xff1a;抢先抓其他选手应抓的牌。 &#xff08;1&#xff09;越序抓牌但并没有看到的&#xff0c;一经发现须马上退回。 &#xff08;2&#xff09;越序抓牌已经看到的但是没有插入手牌中的&#xff0c;除马上退回外&#xff0c;可由裁判员…

OpenHarmony实战开发-文件上传下载性能提升指导。

概述 在开发应用时&#xff0c;要实现高效的客户端跟服务器之间数据交换&#xff0c;文件传输的性能是至关重要的。一个数据交换性能较低的应用会导致其在加载过程中耗费较长时间&#xff0c;在很多的场景造成页面卡顿&#xff0c;极大的影响了用户体验。相反&#xff0c;一个…