Linux学习——线程的控制

目录

​编辑

一,线程的创建

二,线程的退出

1,在子线程内return

 2,使用pthread_exit(void*)

三,线程等待

四,线程获取自己的id值

五,线程取消

六,线程分离


一,线程的创建

在对进程控制之前,首先要做的便是创建一个线程。创建方法如下:

使用的创建方法叫做pthread_create。

参数介绍:

thread:线程id
attr:线程属性,直接设为null
start_routine:函数指针
arg:这个参数会传递进start_routinevoid*参数中。

例子:

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

 void* hander(void* args)//新线程执行的方法
 {
     while(true)
     {
       sleep(1);
       cout << "i am new thread" << endl;
     }
 }

 int main()
 {
   pthread_t td;
   pthread_create(&td, nullptr, hander, nullptr);//创建好新线程以后,新线程会去执行传入的hander方法。

   while(true)//主线程会继续向下执行自己的方法
   {
     sleep(1);
     cout << "i am main thread" << endl;
   }
   return 0;
 }

 执行这个代码以后结果如下:

在这里要注意,在使用g++编译时要加上-lpthread。因为线程库是一个第三方库,但是是安装在系统中的所以只需要-l便可以连接到pthread库

二,线程的退出

1,在子线程内return

  线程的退出有多种方式,先来看看最基本的一种退出方式,代码如下:

​
void *hander(void *args)
{
  string name = static_cast<const char *>(args);
  int cnt = 5;
  while (cnt--)
  {
    cout << "i am new thread" << name << endl;
    sleep(1);
  }
  return nullptr;//最基本的退出线程的方式便是直接在子线程内部使用return的方式退出
}

class data
{
public:
  char buf[64];
  int i;
};

int main()
{

  for (int i = 1; i <= NUM; i++) // 创建一批线程
  {
    data *m = new data();
    snprintf(m->buf, sizeof(m->buf), "%s:%d", "new thread", i);
    pthread_t td;
    pthread_create(&td, nullptr, hander, (void *)m->buf);
  }

  while (true)
  {
    cout << "-- -- -- -- -- -- -- --sucess -- -- -- -- -- -- -- " << endl;
    sleep(1);
  }
  return 0;
}

​

 在使用这种方式退出时,主线程在子线程退出以后还会继续执行。但是如果是子线程不退出而主线程先退出呢?像这样:

​
void *hander(void *args)
{
  string name = static_cast<const char *>(args);
  int cnt = 5;
  while (true)//子线程一直在死循环
  {
    cout << "i am new thread" << name << endl;
    sleep(1);
  }
  return nullptr;
}

class data
{
public:
  char buf[64];
  int i;
};

int main()
{

  for (int i = 1; i <= NUM; i++) // 创建一批线程
  {
    data *m = new data();
    snprintf(m->buf, sizeof(m->buf), "%s:%d", "new thread", i);
    pthread_t td;
    pthread_create(&td, nullptr, hander, (void *)m->buf);
  }

  int cnt = 5;
  while (cnt--)//主线程在cnt减到零时就退出
  {
    cout << "-- -- -- -- -- -- -- --sucess -- -- -- -- -- -- -- " << endl;
    sleep(1);
  }
  return 0;
}

​

 这样的话只要主线程退出了,这个进程都会直接结束。 如下:

 2,使用pthread_exit(void*)

这个函数是线程库提供给我们的专门用于线程退出的函数,他的参数可以直接设置为nullptr。使用方式如下:

void *hander(void *args)
{
  string name = static_cast<const char *>(args);
  int cnt = 5;
  while (cnt--)
  {
    cout << "i am new thread" << name << endl;
    sleep(1);
  }
  pthread_exit(nullptr);//使用pthread_exit()退出线程。
}

class data
{
public:
  char buf[64];
  int i;
};

int main()
{

  for (int i = 1; i <= NUM; i++) // 创建一批线程
  {
    data *m = new data();
    snprintf(m->buf, sizeof(m->buf), "%s:%d", "new thread", i);
    pthread_t td;
    pthread_create(&td, nullptr, hander, (void *)m->buf);
  }

  while (true)
  {
    cout << "-- -- -- -- -- -- -- --sucess -- -- -- -- -- -- -- " << endl;
    sleep(1);
  }
  return 0;
}

使用pthread_exit退出的效果和在子线程内使用return退出的效果一样。 

 ##注意##  线程的退出不能使用exit,因为exit的本质其实是向进程发信号,所以exit是专门用于进程退出的。同样的,线程的退出也不需要返回errno,因为如果一个线程因为异常退出的话整个进程都会退出,进程返回errno就可以了。

三,线程等待

和进程一样,线程也需要等待。等待的目的如下:

1,回收新线程对应的内核资源。

2,接收新线程返回的数据。

线程等待函数: int pthread_join(pthread_t thread, void **retval)

thread:表示要等待线程的pid

reval:接收数据并将数据带出。

使用如下:

class thread
{
public:
  int _num;       // 线程的编号
  char _buf[64];  // 线程的名字
  pthread_t _tid; // 线程的id
};

void *start_routine(void *args)
{

  int cnt = 5;
  while (cnt--)
  {
    sleep(1);
    thread *_td = static_cast<thread *>(args);
    cout << "i am new thread:" << _td->_buf << ":" << _td->_num
         << ":" << _td->_tid << endl;
  }

  pthread_exit(nullptr);//线程退出
}

int main()
{
  vector<thread*> threads;
  for (int i = 1; i <= 10; i++)//创建线程
  {
    thread *td = new thread;
    td->_num = i;
    snprintf(td->_buf, sizeof(td->_buf), "%s-%d", "thread:", i);
    pthread_create(&td->_tid, nullptr, start_routine, (void *)td);
    threads.push_back(td);
  }

  for(auto e:threads)
  {
    void *ret = nullptr;
    pthread_join(e->_tid, &ret);//回收线程
    cout << "等待成功"
         << " tid:" << e->_tid << endl;
  }

  cout << "等待结束" << endl;

  return 0;
}

以上的代码便演示了如何用pthread_join进行线程的等待效果如下:

那该函数里面的里面的返回值有什么作用呢?其实这个返回值就是用来带出退出码的。过程如下:

 添加打印退出码的信息以后结果如下:

那为什么reval的类型是二级指针类型呢?这其实是因为线程结束后,退出信息会写入到线程库内部。线程库内部的退出码便是void*类型的。此时我们要想的便是获取这个退出码了,如何获取呢?因为pthread_join()的返回值是int类型的,所以我们便不能直接让pthread_join()直接返回一个void*类型的变量,所以只能自己在用户层定义一个void*类型的retval然后retval的地址传入进去获取返回值了。

四,线程获取自己的id值

使用 pthread_t pthread_self(void)可以获取到当前线程的id值。

示例代码:

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

void *Done(void *args)
{
    uint64_t i = (uint64_t)args;

    string name = "thread_" + to_string(i);

    sleep(1);

    cout << name << "id :" << pthread_self() << endl;//使用pthread_self()打印线程id值。

    
}

int main()
{
    vector<pthread_t> wait;
    for (uint64_t i = 1; i <= 4; i++)
    {
        pthread_t td;
        pthread_create(&td, nullptr, Done, (void *)i); // 创建线程
        wait.push_back(td);

        sleep(2);
    }

    for (auto e : wait) // 等待线程
    {
       pthread_join(e, nullptr);
        
    }
    return 0;
}

 

如果用16进制打印便是下面这样的: 

其实线程的id就是一些地址。

五,线程取消

 进行线程取消的函数叫做pthread_cancel(pthread_t thread)。线程取消的前提是线程先运行起来,然后才能取消。

实验代码:创建线程,然后取消一半线程,观察现象。

class thread
{
public:
  int _num;       // 线程的编号
  char _buf[64];  // 线程的名字
  pthread_t _tid; // 线程的id
};

void *start_routine(void *args)
{

  int cnt = 5;
  while (cnt--)
  {
    sleep(1);
    thread *_td = static_cast<thread *>(args);
    cout << "i am new thread:" << _td->_buf << ":" << _td->_num
         << ":" << _td->_tid << endl;
  }

  pthread_exit((void*)100);
}

int main()
{
  vector<thread *> threads;
  for (int i = 1; i <= 10; i++)
  {
    thread *td = new thread;
    td->_num = i;
    snprintf(td->_buf, sizeof(td->_buf), "%s-%d", "thread:", i);
    pthread_create(&td->_tid, nullptr, start_routine, (void *)td->_buf);
    threads.push_back(td);
  }

  for (int i = 0;i<threads.size()/2;i++)//取消一半的线程
  {
    pthread_cancel(threads[i]->_tid);
  }

    for (auto e : threads)//等待
    {
      void *ret = nullptr;
      pthread_join(e->_tid, &ret);
      cout << "等待成功"
           << " tid:" << e->_tid << "quit code: " << (long long)(ret) << endl;

      delete e;
    }

  cout << "等待结束" << endl;

  return 0;
}

 运行结果如下:

可以看到如果取消线程,那线程还是会被等待然后退出,退出码是-1。其实这是一个宏:

六,线程分离

        线程分离使用到的函数 int pthread_detach(pthread_t thread)。先来说明一下,新创建的线程默认是joinable的。但是如果我的主线程并不关心当前的线程的返回值,那当前的线程便与我无关。那我的主线程去等待当前的线程便对我的主线程是一种负担。这个时候便可以来进行线程分离。线程的分离方式有两种:1,主线程去分离子线程    2,子线程自己进行分离。

示例代码:

1,主线程进行分离

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

void* Done(void* args)
{
    uint64_t i = (uint64_t)args;

    string name = "thread_" + to_string(i);

    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        cout << name << "running....." << endl;
        sleep(3);
    }
}

int main()
{
    vector<pthread_t> wait;
    for (uint64_t i = 1; i <= 4; i++)
    {
        pthread_t td;
        pthread_create(&td, nullptr, Done, (void*)i);//创建线程
        wait.push_back(td);
        sleep(3);//先休眠三秒,再进行线程分离
        pthread_detach(td);//主线程子集分离
    }

    for(auto e:wait)//等待线程
    {
       int n =  pthread_join(e,nullptr);
       cout << n << " " << endl;//打印等待的返回值,0表示成功,其它表示失败。
    }
        return 0;
}

2,子线程自己主动分离 

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

void *Done(void *args)
{
    uint64_t i = (uint64_t)args;

    string name = "thread_" + to_string(i);

    pthread_detach(pthread_self()); // 子线程自己自动分离

    int cnt = 5;
    while (cnt--)
    {
        cout << name << "running....." << endl;
        sleep(1);
    }
}

int main()
{
    vector<pthread_t> wait;
    for (uint64_t i = 1; i <= 4; i++)
    {
        pthread_t td;
        pthread_create(&td, nullptr, Done, (void *)i); // 创建线程
        wait.push_back(td);
    }

    for (auto e : wait) // 等待线程
    {
        int n = pthread_join(e, nullptr);
        cout << n << " " << endl; // 打印等待的返回值,0表示成功,其它表示失败。
    }
    return 0;
}

  

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全面的 DevSecOps 指南:有效保护 CI/CD 管道的关键注意事项

数字化转型时代带来了对更快、更高效、更安全的软件开发流程的需求。DevSecOps&#xff1a;一种将安全实践集成到 DevOps 流程中的理念&#xff0c;旨在将安全性嵌入到开发生命周期的每个阶段 - 从代码编写到生产中的应用程序部署。DevSecOps 的结合可以带来许多好处&#xff0…

抖音视频提取gif怎么做?分分钟帮你生成gif

通过将视频转换成gif动图的方式能够方便的在各种平台上分享、传播。相较于视频文件&#xff0c;gif动图的体积更小&#xff0c;传播起来更方便&#xff0c;能够吸引大众的注意力。下面&#xff0c;就来给大家分享一个gif图片制作&#xff08;https://www.gif.cn/&#xff09;的…

mybatisplus的条件构造器

条件构造器wrapper&#xff0c;主要用于构造sql语句的where条件&#xff0c;他更擅长这个&#xff0c;但也可以用于构造其他类型的条件&#xff0c;比如order by、group by等。 条件构造器的使用经验&#xff1a; 基于QueryWrapper的查询 练习1. void testQueryWrapper(){Q…

服务器集群 -- nginx配置tcp负载均衡

当面临高流量、高可用性、水平扩展、会话保持或跨地域流量分发等需求时&#xff0c;单台服务器受限于硬件资源、性能有限不能满足应用场景的并发需求量时&#xff0c;引入负载均衡器部署多个服务器共同处理客户端的并发请求&#xff0c;可以帮助优化系统架构&#xff0c;提高系…

猫咪挑食怎么治?排行榜靠前适口性好的主食冻干推荐

在如今&#xff0c;养猫人士几乎都将自己的小猫咪视作珍宝&#xff0c;宠溺有加。但宠爱过度有时也会导致猫咪养成挑食的坏习惯。猫咪挑食怎么治呢&#xff1f;今天&#xff0c;我要分享一个既能让猫咪不受苦&#xff0c;又能纠正挑食问题的方法。 一、为什么猫会挑食呢&#x…

Linux调试器--gdb的介绍以及使用

文章目录 1.前言 ✒️2.介绍gdb✒️3.Debug模式和Release模式的区别✒️4.如何使用gdb✒️1️⃣.在debug模式下编译2️⃣.进入调试3️⃣ .调试命令集合⭐️⭐️ 1.前言 ✒️ &#x1f557;在我们之前的学习中已经学会了使用vim编译器编写c/c代码&#xff0c;但是对于一个程序员…

ThreadLocal出现内存泄露原因分析

ThreadLocal 导致内存泄漏的主要原因是它的工作方式。在 Java 中&#xff0c;ThreadLocal 通过维护一个以 Thread 为键&#xff0c;以用户设置的值为值的映射来工作。每个线程都拥有其自身的线程局部变量副本&#xff0c;不同线程间的这些变量互不干扰。这个映射是存储在每个 T…

sql-mysql可视化工具Workbench导入sql文件

mysql可视化工具Workbench导入sql文件 1、打开workbench2、导入sql文件3、第一行加上库名4、开始运行 1、打开workbench 2、导入sql文件 3、第一行加上库名 4、开始运行

Java学习笔记------拼图游戏

图形化界面GUI GUI&#xff1a;Graphical User Interface&#xff08;图像用户接口&#xff09;&#xff0c;指采用图形化的方式显示操作界面 两套体系&#xff1a;AWT包中和Swing包中 组件 JFrame&#xff1a;最外层的窗体 JMenuBar&#xff1a;最上层菜单 JLaber&#…

微信小程序开发系列(二十四)·wxml语法·列表渲染·wx:for-item 和 wx:for-index

目录 1. 如果需要对默认的变量名和下标进行修改&#xff0c;可以使用wx:for-item 和 wx:for-index 2. 将 wx:for 用在 标签上&#xff0c;以渲染一个包含多个节点的结构块 方法一 方法二 3. 总结 3.1 wx:for-item 和 wx:for-index总结 3.2 总结 1. 如果需要对默…

从mysql 数据库表导入数据到elasticSearch的几种方式

从MySQL数据库导入数据到Elasticsearch有几种方式&#xff0c;主要包括以下几种&#xff1a; 1. 使用Logstash&#xff1a; Logstash是一个开源的数据收集引擎&#xff0c;可以用来从不同的数据源导入数据到Elasticsearch。它具有强大的数据处理能力和插件生态系统&…

xlsx.js读取本地文件,按行转成数组数据

1.下包 //1. npm install xlsx //2. yarn add xlsx2.结构 <template><input type"file" change"onFileChange" /> </template>3.代码 <script> import * as XLSX from xlsxexport default {methods: {onFileChange (event) {/…

【“双碳”目标】Acrel-2000Z分布式光伏发电监测系统解决方案

1 概述 “十四五”期间&#xff0c;随着“双碳”目标提出及逐步落实&#xff0c;本就呈现出较好发展势头的分布式光伏发展有望大幅提速。就“十四五”光伏发展规划&#xff0c;国家发改委能源研究所可再生能源发展中心副主任陶冶表示&#xff0c;“双碳”目标意味着国家产业结…

【JS逆向学习】猿人学 第五题 js混淆 乱码

逆向目标 网址&#xff1a;https://match.yuanrenxue.cn/match/5接口&#xff1a;https://match.yuanrenxue.cn/api/match/5?page2&m1709806560791&f1709806560000参数&#xff1a; Cookie(m、RM4hZBv0dDon443M)payload(m、f) 逆向过程 老规矩&#xff0c;上来先分…

Java后端八股文之Redis

文章目录 1. Redis是什么&#xff1f;2. Redis为什么这么快&#xff1f;3. 为什么要使用缓存&#xff1f;4. Redis几种使用场景&#xff1a;5. Redis的Zset底层为什么要使用跳表而不是平衡树、红黑树或者B树&#xff1f;6.Redis持久化6.1 什么是RDB持久化6.1.1RDB创建快照会阻塞…

修改Android打包apk的名字和目录

app打包生成apk后通常需要进行备份&#xff0c;但是要区分好哪个apk是什么版本的、什么时候打包的&#xff0c;以方便以后区分使用。 最开始的想法是把版本号、创建时间这些加在apk文件名上即可&#xff0c;但是公司要求apk使用一个固定的名称&#xff0c;那我怎么保存版本号信…

OpenGL学习——19.模板测试

前情提要&#xff1a;本文代码源自Github上的学习文档“LearnOpenGL”&#xff0c;我仅在源码的基础上加上中文注释。本文章不以该学习文档做任何商业盈利活动&#xff0c;一切著作权归原作者所有&#xff0c;本文仅供学习交流&#xff0c;如有侵权&#xff0c;请联系我删除。L…

Vue+OpenLayers7入门到实战:OpenLayers鼠标移动事件使用,实现鼠标移动到点位上方后高亮显示点位要素

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上监听鼠标移动事件,并简单实现鼠标移动到点位上方后高亮显示点位要素的功能,带领大家快速上手OpenLayers鼠标移动事件的应用。 二、依赖和使用 "ol": &quo…

趣味看图-Linux 文件系统的组成

/&#xff08;根目录&#xff09;&#xff1a;根目录是Linux文件系统中的顶级目录。所有其他目录都是根目录的子目录&#xff0c;使其成为整个文件系统的父目录。 /bin&#xff1a;包含启动系统和执行基本操作所需的基本二进制可执行文件。这些对所有用户都可用。 /boot&…

利用yaml文件部署NacosXxl-job到阿里云的ACK

背景介绍 随着容器化的技术成熟落地&#xff0c;拥抱各种成熟的容器化集群平台是加速我们落地的必然之路&#xff0c;目前国内以阿里云、华为云、腾讯云为平台的供应商为主&#xff0c;国外则以AWS&#xff0c;Azure为主&#xff0c;让我们借助平台已有的优势进行快速落地提高…