_Linux多线程-线程控制篇

news2024/10/7 8:27:21

文章目录

  • 1. POSIX线程库
  • 2. 创建线程
  • 3. 线程ID及进程地址空间布局
  • 4. 线程等待
  • 5. 线程终止
    • pthread_ exit
    • pthread_ cancel
  • 6. 分离线程
  • 7. 总结

1. POSIX线程库

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

2. 创建线程

   #include <pthread.h>
   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;失败返回错误码。
  • 测试代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

void* threadRoutine(void* args)
{
    //pthread_self() 获取该线程的id
    while(true)
    {
        sleep(1);
        cout<<"这是一个新线程: "<<(char*)args<<"runing..."<<pthread_self()<<endl;
    }
}
int main()
{
    //创建一个线程
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");

    while(1)
    {
        cout<<"main thread"<<endl;
        sleep(1);
    }
    return 0;
}
  • 结果展示:线程创建成功
    在这里插入图片描述

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

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID(LWP)不是一回事。

  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。

  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

    • pthread_t pthread_self(void);
  • 对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址
    在这里插入图片描述
    —图片来源于资料

4. 线程等待

  • 为什么需要线程等待?
    • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
    • 创建新的线程不会复用刚才退出线程的地址空间。

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

  • 我们可以利用value_ptr;返回我们想要在线程中收集的信息
  • 测试代码:
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

void *threadRoutine(void *args)
{
    // pthread_self() 获取该线程的id
    int *data = new int[10];
    int count = 0;
    while (true)
    {
        sleep(1);
        cout << "这是一个新线程: " << (char *)args << "runing..." << pthread_self() << endl;

        data[count] = count;
        ++count;
        if (count == 2)
            break;
        // return (void *)10;  //强转为void*
    }

    return data;
}
int main()
{
    // 创建一个线程
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    int count = 0;
    while (1)
    {
        cout << "main thread" << endl;
        ++count;
        if (count == 3)
        {
            int *ret = nullptr; // ret指针变量
            pthread_join(tid, (void **)&ret);
            // cout << "ret: " << (long long)ret << endl;  //Linux下机器是64位
            for (int i = 0; i < 2; ++i)
            {
                cout << "ret: " << (long long)(ret + i) << endl; // Linux下机器是64位
            }
            break;
        }
    }
    return 0;
}
  • 结果视图:
    在这里插入图片描述

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

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

5. 线程终止

  • 如果需要只终止某个线程而不终止整个进程,可以有三种方法:
      1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit(它是进程退出了)。
      1. 线程可以调用pthread_ exit终止自己。
      1. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_ exit

功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

  • 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
  • 代码块:
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;


void *threadRoutine(void *args)
{
    // pthread_self() 获取该线程的id
    int count = 0;
    while (true)
    {
        sleep(1);
        cout << "这是一个新线程: " << (char *)args << "runing..." << pthread_self() << endl;

        ++count;
        if (count == 2) pthread_exit((void*)11); 
    }
}
int main()
{
    // 创建一个线程
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    int count = 0;
    while (1)
    {
        cout << "main thread" << endl;
        ++count;
        if (count == 3)
        {
            int *ret = nullptr; // ret指针变量
            pthread_join(tid, (void **)&ret);
            cout << "ret: " << (long long)ret << endl;  //Linux下机器是64位
            break;
        }
    }
    return 0;
}
  • 结果视图:
    在这里插入图片描述

pthread_ cancel

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

  • 代码块:
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;


void *threadRoutine(void *args)
{
    // pthread_self() 获取该线程的id
    while (true)
    {
        sleep(1);
        cout << "这是一个新线程: " << (char *)args << "runing..." << pthread_self() << endl; 
    }
}
int main()
{
    // 创建一个线程
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    int count = 0;
    while (1)
    {
        cout << "main thread" << endl;
        sleep(3);
        ++count;
        pthread_cancel(tid); //取消一个线程
        if(count==4) break;
    }
    return 0;
}
  • 结果视图:
    在这里插入图片描述

6. 分离线程

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

     #include <pthread.h>
     int pthread_detach(pthread_t thread);
    
  • 可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

      pthread_detach(pthread_self());
    
  • 注意: joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

7. 总结

    1. 线程谁先运行与调度器相关
    1. 线程一旦异常,都可能导致整个进程整体退出
    1. 线程的输入和返回值问题
    1. 线程异常退出的理解
    1. _ _thread : 修饰全局变量,带来的结果就是让每一个线程各自拥有一个全局的变量 – 线程的局部存储。
  • 线程在创建并执行的时候,线程也是需要进行等待的,如果主线程如果不等待,即会引起类似于进程的僵尸问题,导致内存泄漏。

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

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

相关文章

三角函数在编程中的实际运用—永劫无间脚本

三角函数在编程中的实际运用—永劫无间脚本前言需求思路代码■ 转义码■ 源码具体讲解三角函数计算相对移动求余跳过不需要的位置成品最后前言 义务教育下&#xff0c;年轻人从初中就开始学三角函数却半辈子也没用上&#xff0c;除了特殊行业&#xff0c;做开发的可能也就大学…

Nginx web服务器入门及其在Linux中的搭建

目录 ​编辑 一、Nginx基本概述 1.介绍 2.优点 3.应用场景 &#xff08;1&#xff09;负载均衡 &#xff08;2&#xff09;代理缓存 &#xff08;3&#xff09;静态资源 &#xff08;4&#xff09;安全应用场景 4.Nginx的组成 &#xff08;1&#xff09;Nginx二进制…

Canal同步数据

canal同步数据 canal可以用来监控数据库数据的变化&#xff0c;从而获得新增数据&#xff0c;或者修改的数据。 canal是应阿里巴巴存在杭州和美国的双机房部署&#xff0c;存在跨机房同步的业务需求而提出的。 阿里系公司开始逐步的尝试基于数据库的日志解析&#xff0c;获取…

(9)Qt中信号与槽重载的解决方案

信号与槽重载的解决方案 一、通过函数指针解决 //信号 void (Me::*funchungury)() &Me::hungury; void (Me::*funchungury_QString)(QString) &Me::hungury; //槽 void (Me::*funceat)() &Me::eat; void (Me::*funceat_QString)(QString) &Me::eat;//有参…

Oracle与MySQL语法转换

前言 Oracle与MySQL语法转换 场景&#xff1a;系统改造&#xff0c;需要由Oracle切换为MySQL&#xff0c;因而要对代码中的Oracle语法的sql调整为MySQL语法 博客地址&#xff1a;芒果橙的个人博客 【http://mangocheng.com】 sysdate–当前日期 Oracle 使用sysdate select s…

hdl_graph_slam代码解析

hdl SLAM和定位的关系&#xff1a;HDL和cartographer一样&#xff0c;是离线建图的 整个SLAM系统的架构 包含四个节点&#xff1a; 预处理、 帧匹配、hdl_slam、地面检测 输入点云首先经过预处理进行降采样&#xff0c;然后传给下一个节点。帧匹配通过迭代获取两帧之间运动变化…

【SpringCloud01】微服务架构入门

1.微服务架构理论入门 SpringCloud微服务 2.Boot和Cloud版本选型 上篇&#xff1a;SpringBoot2.X版和SpringCloud H版 下篇&#xff1a;SpringCloud Alibaba 官网强烈推荐SpringBoot2.0以上的版本 Cloud与Boot之间的版本关系 技术选型相关的网站使用在线解析json字符串 由于…

第2章 马尔可夫决策过程

2.1 马尔可夫决策过程&#xff08;上&#xff09; Markov Decision Process&#xff08;MDP&#xff09; Markov Decision Process can model a lot of real-world problem. It formally describes the framework of reinforcement learningUnder MDP, the environment is ful…

Promise 实现 (从简易版到符合Promise A+规范)

前言 手写 Promise 是面试的时候大家都逃避的送命题&#xff0c;在学些了解后发现通过实现源码更能将新一代的异步方案理解的通透&#xff0c;知其然知其所以然的运用。 如果直接将源码贴到此处势必不能有更大的收获&#xff0c;下面就按实现版本来看做简要分析。 回顾 Prom…

SpringBoot测试类编写

前置要求: a.测试类上需要的注解 SpringBootTest AutoConfigureMockMvc Slf4j b.引入MockMvc类 Autowired private MockMvc mockMvc; c.如果需要前置条件可以用before注解 1.get/delete请求 // 查询Testvoid testQuery() throws Exception {String content mockMvc.perfor…

Django(15):身份和权限认证

目录1.Django中的身份认证模块1.1 用户模型1.2 认证模块1.3 项目搭建演示2.权限管理架构2.1 权限相关数据模型2.2 权限相关功能函数2.3 权限分配函数2.4 权限设置3.资源访问管理1.Django中的身份认证模块 1.1 用户模型 Django中有内建的用户模块django.contrib.auth.models.U…

2022 CNCC 中国计算机大会参会总结

前言 第 19 届 CNCC 于2022年12月8-10日召开&#xff0c;本届大会为期三天&#xff0c;首次采取全线上举办形式&#xff0c;主题为“算力、数据、生态”&#xff0c;重点在保持多样性、聚焦热点前沿话题、平衡学术界和产业界参与等维度展开讨论。大会由CCF会士、中国科学院院士…

【SpringBoot】一文带你入门SpringBoot

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;【Spring】 &#x1f96d;本文内…

【职场进阶】做好项目管理,先从明确职责开始

优秀的项目管理一定是高效协调各方资源、反馈及时、调整迅速的。 同时可以做到让参与各方在整个项目过程中张弛有序、愉快合作&#xff0c;最终实现产品项目的效益最大化。 那什么是项目呢&#xff1f; 项目是为向客户提供独特的产品或服务而进行的临时性任务&#xff0c;项目有…

TypeScript 对象key为number时的坑

首先在js的对象中有一个设定&#xff0c;就是对象的key可以是字符串&#xff0c;也可以是数字。 不论key是字符串还是数字&#xff0c;遍历对象key的时候&#xff0c;这个key会变成字符串 通过[] 操作符访问key对应值时候&#xff0c;不论是数字还是字符串都转成了 字符串的k…

Chromedriver安装教程

第一步 查看你当前Chrome浏览器的版本&#xff0c;如下图所示&#xff1a; 第二步 查看当前Chrome浏览器的版本号&#xff0c;如下图所示,版本 108.0.5359.125&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09;中的&#xff0c;108就是我们的版本号。 第三…

VTK-PointPlacer

前言&#xff1a;本博文主要研究VTK中点转换到曲面上的应用&#xff0c;相关的接口为vtkPolygonalSurfacePointPlacer&#xff0c;为深入研究将基类vtkPointPlacer开始讲解。主要应用为在PolyData表面进行画线。 vtkPointPlacer 描述&#xff1a;将2D display位置转换为世界坐…

ospf知识点汇总

OSPF &#xff1a; 开放式最短路径优先协议使用范围&#xff1a;IGP 协议算法特点&#xff1a; 链路状态型路由协议&#xff0c;SPF算法协议是否传递网络掩码&#xff1a;传递网络掩码协议封装&#xff1a;基于IP协议封装&#xff0c;协议号为 89一.OSPF 特点1.OSPF 是一种典型…

基于javaweb(springboot+mybatis)网上酒类商城项目设计和实现以及文档报告

基于javaweb(springbootmybatis)网上酒类商城项目设计和实现以及文档报告 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏…

【Linux】Linux项目自动化构建工具—make/Makefile

目录一.什么是make/MakefileMakefilemake二.Makefile逻辑1.简单依赖2.复杂依赖三.make指令1.make的使用2.clean清理3.伪目标4.make如何确定是否编译访问时间的影响修改时间的影响一.什么是make/Makefile Makefile 在Windows下&#xff0c;我们使用VS、VS Code这些ide编写C/C程…