【Linux 20】线程控制

文章目录

  • 🌈 一、创建线程
    • ⭐ 1. 线程创建函数
    • ⭐ 2. 创建单线程
    • ⭐ 3. 给线程传参
    • ⭐ 4. 创建多线程
    • ⭐ 5. 获取线程 ID
  • 🌈 二、终止线程
    • ⭐1. 使用 return 终止线程
    • ⭐ 2. 使用 pthread_exit 函数终止线程
    • ⭐ 3. 使用 pthread_cancel 函数终止线程
  • 🌈 三、等待线程
    • ⭐ 1. 线程等待函数
    • ⭐ 2. 获取返回值
  • 🌈 四、分离线程
    • ⭐ 1. 线程分离概念
    • ⭐ 2. 线程分离函数

  • 引入线程库:要使用线程相关的函数,需要使用 #include <pthread.h> 引入原生线程库。
  • 链接线程库:引入线程库后,在编译程序时还需要使用 -lpthread 选项链接原生线程库。

🌈 一、创建线程

⭐ 1. 线程创建函数

  • 线程创建成功时返回 0,创建失败时返回错误码。
#include <pthread.h>int pthread_create(pthread_t *thread, 					/* 输出型参数,用以获取创建成功的线程 ID */const pthread_attr_t *attr,			/* 设置创建线程的属性,使用 nullptr 表示使用默认属性 */void *(*start_routine) (void *), 	/* 函数地址,该线程启动后需要去执行的函数 */void *arg);							/* 线程要去执行的函数的形参,没参数时可填写 nullptr */

⭐ 2. 创建单线程

  • 当一个程序启动时,其中至少有一个线程在工作,这个线程就叫做主线程。
  • 主线程是产生其他子线程的线程,通常主线程必须最后完成某些执行操作。
  • 让主线程调用 pthread_create 函数创建一个新线程,然后让新线程跑去执行 thread_run 函数,而主线程则继续执行后续代码。
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;// 新线程
void* thread_run(void* args)
{while (true){cout << "新 线程正在运行" << endl;sleep(1);}
}// 主线程
int main()
{// 记录线程 idpthread_t tid;// 创建新线程,并让新线程去执行 thread_run 函数pthread_create(&tid, nullptr, thread_run, nullptr);// 主线程继续执行自己后续的代码while (true){cout << "主 线程正在运行" << endl;sleep(1);}return 0;
}
  • 运行代码后会发现,主线程和新线程会交替执行自己的任务。

  • 在程序运行过程中,新开一个窗口使用ps axj命令查看当前进程的信息时,由于这两个线程都属于同一个进程,因此只能看到一个进程的信息。

image-20240912174356270

  • 如果想要查看线程 (轻量级进程) 的相关信息,可以使用 ps -aL 查看指定进程内部的轻量级进程的信息。
    • LWP (Light Weight Process) 表示的就是线程 (轻量级进程) 的 ID,其中主线程的 LWP 值等同于进程的 PID 值。

image-20240912175003416

⭐ 3. 给线程传参

  • 由于 pthread_create 函数创建出来的线程所执行的函数的形参类型是 void* 的,可以传递任何类型的参数,也能传对象给线程使用
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;class thread_data
{
public:thread_data(const string& thread_name):_thread_name(thread_name){}string thread_name(){return _thread_name;}private:    string _thread_name;
};void* thread_run(void* args)
{thread_data* td = static_cast<thread_data*>(args);while (true){cout << "新线程 " << td->thread_name() << " 正在运行" << endl;sleep(1);}
}int main()
{thread_data* td = new thread_data("thread 1");// 创建新线程,将 td 对象作为 thread_tun 函数的参数pthread_t tid;pthread_create(&tid, nullptr, thread_run, td);while (true){cout << "主线程正在运行" << endl;sleep(1);}return 0;
}

⭐ 4. 创建多线程

  • 每个线程都由各自的线程 ID 所管理着,创建多线程时可以使用一个数据结构将每个创建出来的线程的 ID 管理起来。

举个例子

  • 创建 5 个线程,使用数组将这些线程的线程 ID 管理起来,让这些线程都去执行 thread_run 函数。
#include <string>
#include <vector>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::to_string;class thread_data
{
public:thread_data(const string& thread_name):_thread_name(thread_name){}string thread_name(){return _thread_name;}private:    string _thread_name;
};void* thread_run(void* args)
{thread_data* td = static_cast<thread_data*>(args);while (true){cout << "新线程 " << td->thread_name() << " 正在运行" << endl;sleep(1);}
}const int thread_num = 5;   // 创建 5 个线程int main()
{// 存储创建的所有线程的线程 IDvector<pthread_t> tids(thread_num);// 创建 5 个线程并给每个线程传递一个 thread_data 对象for (size_t i = 0; i < thread_num; i++){string thread_name = "thread " + to_string(i + 1);thread_data* td = new thread_data(thread_name);pthread_create(&tids[i], nullptr, thread_run, td);}while (true){cout << "主线程正在运行" << endl;sleep(1);}return 0;
}

  • 再使用 ps -aL 指令可以查到 主 + 新 这 6 个线程的情况。

image-20240912200440834

⭐ 5. 获取线程 ID

  1. 可通过 pthread_create 函数的第一个输出型参数获得创建的线程的 ID。
  2. 让线程执行的函数调用 pthread_self 函数获取调用该函数的线程的 ID。
    • 哪个线程调用该函数就返回哪个线程的线程 ID。
#include <pthread.h>pthread_t pthread_self(void);
  • 注:pthread_self 函数获得的线程 ID 不等于内核的 LWP 值,pthread_self 函数获得的是用户级原生线程库的线程 ID,而 LWP 是内核的轻量级进程ID,它们之间是一对一的关系。

🌈 二、终止线程

  • 如果只是想终止某个线程而不是整个进程,可以有如下 3 种方法。
  1. 使用 return 终止线程:非主线程可以在执行的函数中使用 return 终止当前线程。
  2. 使用 pthread_exit 终止线程:线程自己可以调用该函数终止自己。
  3. 使用 pthread_cancel 终止线程:该函数能通线程 ID 终止任意线程。

⭐1. 使用 return 终止线程

  • 在线程调用的函数中使用 return 退出当前线程,但是在main函数中使用return代表整个进程退出,也就是说只要主线程退出了那么整个进程就退出了,此时该进程曾经申请的资源就会被释放,而其他线程会因为没有了资源,自然而然的也退出了。

举个例子

#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;void* thread_run(void* args)
{string thread_name = static_cast<const char*>(args);// 让新线程运行 5 秒之后退出for (size_t i = 0; i < 5; i++){cout << thread_name << " 正在运行" << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");while (true){cout << "主线程正在运行" << endl;sleep(1);}return 0;
}

⭐ 2. 使用 pthread_exit 函数终止线程

#include <pthread.h>void pthread_exit(void *retval);	// retval 是线程在退出时,线程所调用函数的返回值
  • pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用malloc 分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了。

举个例子

#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;void* thread_run(void* args)
{string thread_name = static_cast<const char*>(args);// 让新线程运行 5 秒之后退出for (size_t i = 0; i < 5; i++){cout << thread_name << " 正在运行" << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");while (true){cout << "主线程正在运行" << endl;sleep(1);}return 0;
}

⭐ 3. 使用 pthread_cancel 函数终止线程

  • 该函数可以根据线程 ID 终止任意线程,可以用来终止自己或其他线程,不过一般建议是拿来终止其他线程。

举个例子

  • 在新线程运行 5s 后,用主线程将新线程终止。
#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;void* thread_run(void* args)
{string thread_name = static_cast<const char*>(args);while (true){cout << thread_name << " 正在运行" << endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");cout << "主线程正在运行" << endl;sleep(5);pthread_cancel(tid);    // 主线程终止新线程cout << "新线程已被终止" << endl;return 0;
}

image-20240914154513668

🌈 三、等待线程

  • 和进程等待一样,一个线程在被创建出来时,也需要被等待。
  • 新线程在退出时,主线程也要回收新线程所持有的资源,否则会产生类似进程的 “僵尸” 问题,从而造成内存泄漏。
  • 新线程退出时,主线程需要根据新线程的返回值,获取新线程的退出信息。
  • 此外,主线程不关心新线程异常退出的情况,因为线程一旦异常进程立马就会挂掉,只会关心进程退出的相关信息。

⭐ 1. 线程等待函数

  • 该函数在等待成功时会返回 0,失败时返回对应的错误码。
#include <pthread.h>int pthread_join(pthread_t thread,	/* 被等待的线程的线程 ID */ void **retval);		/* 获取被等待的线程在退出时的返回值 */
  • 调用该函数的线程将阻塞等待新线程,直到所等待的的新线程终止为止,被等待的线程以不同的方式终止时,通过 pthread_join 得到的终止状态也是不同的。

举个例子

#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;void* thread_run(void* args)
{string thread_name = static_cast<const char*>(args);// 让新线程运行 5 秒之后退出for (size_t i = 0; i < 5; i++){cout << thread_name << " 正在运行" << endl;sleep(1);}return nullptr;
}int main()
{// 主线程创建新线程pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");cout << "主线程正在运行" << endl;// 主线程阻塞等待 tid 所描述的新线程int n = pthread_join(tid, nullptr);cout << "主线程运行完毕, n: " << n << endl;return 0;
}

⭐ 2. 获取返回值

  • pthread_create 创建出的线程所调用函数的返回值是一个 void* 类型的值,而 pthread_join 函数的第二个输出型参数是 void** 类型,就是为了获取新线程所调用函数的返回值。

1. 举个例子

  • 让新线程调用的函数返回一个字符串。
#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;void* thread_run(void* args)
{string thread_name = static_cast<const char*>(args);// 让新线程运行 5 秒之后退出for (size_t i = 0; i < 5; i++){cout << thread_name << " 正在运行" << endl;sleep(1);}return (void*)"thread 1 done";  			// 返回这个字符串常量的起始地址// pthread_exit((void*)"thread 1 done");	// 这两种方法返回的内容一样
}int main()
{// 主线程创建新线程pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");cout << "主线程正在运行" << endl;// 主线程阻塞等待 tid 所描述的新线程void* ret = nullptr;            // 用来接收线程退出时的退出信息int n = pthread_join(tid, &ret);cout << "主线程运行完毕, n: " << n << endl;cout << "主线程获取到的新线程的退出信息为: " << (char*)ret << endl;return 0;
}

2. 线程退出时可以返回对象

  • 由于线程所调用的函数的返回值是 void* 类型的,意味着线程在完事之后还可以返回一个处理好的对象。
#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;// 保存线程退出时的相关信息
class thread_return
{
public:pthread_t _id;   // 哪个线程退了string _info;    // 退出时想传达的信息int _code;       // 线程退出的信息
public:thread_return(pthread_t id, const string& info, int code): _id(id), _info(info), _code(code){}
};// 新线程所执行的函数
void* thread_run(void* args)
{string thread_name = static_cast<const char*>(args);// 让新线程运行 5 秒之后退出for (size_t i = 0; i < 5; i++){cout << thread_name << " 正在运行" << endl;sleep(1);}thread_return* ret = new thread_return(pthread_self(), "线程正常退出", 10);pthread_exit(ret);
}int main()
{// 主线程创建新线程pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");cout << "主线程正在运行" << endl;// 主线程阻塞等待 tid 所描述的新线程void* ret = nullptr;            // 用来接收线程退出时的退出信息int n = pthread_join(tid, &ret);cout << "主线程运行完毕, n: " << n << endl;thread_return* r = static_cast<thread_return*>(ret);cout << "主线程获取到的新线程的退出信息为" << endl;cout << "id: " << r->_id << ", info: " << r->_info << ", code: " << r->_code << endl;delete r;return 0;
}

image-20240914153537503

  • 这种特性使得我们可以将任务分配给线程运行,运行完毕之后再将结果返回。

🌈 四、分离线程

⭐ 1. 线程分离概念

  • 一般情况下,主线程是需要阻塞等待新线程的,如果不等待线程退出,就有可能造成 “僵尸” 问题。
  • 但如果主线程就是不想等待新线程退出,也不关心新线程的返回值,此时就可以将新线程分离,被分离的线程在退出时会自动释放资源
  • 分离出的新线程依旧要使用进程的资源,且依旧在该进程内运行,甚至这个新线程崩溃了也会影响整个进程。线程分离只是让主线程不需要再阻塞等待整个新线程罢了,在新线程退出时系统会自动回收该线程所持有的资源。
  • 一个线程可以将其他线程分离出去,也可以将自己分离出去。
  • 等待和分离会发生冲突,一个线程不能既是可被等待的又是分离出去的。
  • 虽然分离出去的线程已经不归主线程管了,但一般还是建议让主线程最后再退出
  • 分离出去的线程可以被 pthread_cancel 函数终止,但不能被 pthread_join 函数等待。

⭐ 2. 线程分离函数

#include <pthread.h>int pthread_detach(pthread_t thread);	// thread 是要分离出去的线程的 ID
  • 线程分离成功时返回 0,失败时则返回对应错误码。

举个例子

  • 主线程将创建好的 thread 1 线程分离出去,之后主线程就不需要再 join 新线程了。
#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>using std::cout;
using std::endl;
using std::string;const int num = 5;// 新线程
void* thread_run(void* args)
{string thread_name = static_cast<const char*>(args);// pthread_detach(tid); // 新线程也可以将自己分离出去for (int i = 0; i < num; i++){cout << thread_name << " 正在运行" << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{// 创建新线程pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");pthread_detach(tid); // 主线程将目标进程分离出去while (true){cout << "I am a main thread" << endl;sleep(1);}return 0;
}

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

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

相关文章

【tomcat】tomcat学习笔记

文章目录 1.tomcat乱码问题1.1 linux乱码中文显示乱码问号问题1.2windows乱码1.2.1 方式一1.2.2方式二 1.3 Idea中运行tomcat乱码问题 2. 获取tomcat启动端口号3. idea运行tomcat 的配置问题4.dockerfile构建tomcat镜像问题4.1 替换端口号 5.启动多个tomcat方法6.修改tomcat JA…

Oracle数据库安装与SQL*Plus使用

一、实验过程 1、安装完数据库服务器程序后&#xff0c;查看系统服务启动状况并截图。 2、启动 SOL Plus工具,分别以SYS用户和 SYSTEM用户登录数据库&#xff0c;并解锁scott用户&#xff0c;用scott用户登录。每次登录完成后用show user命令查看当前用户&#xff0c;并截图。…

git reflog 和 git log 的详解和区别

文章目录 1. git log 介绍基本用法&#xff1a;输出内容&#xff1a;常见选项&#xff1a;git log 的局限性&#xff1a; 2. git reflog 介绍基本用法&#xff1a;输出内容&#xff1a;git reflog 输出字段&#xff1a;常见选项&#xff1a;主要用途&#xff1a;示例&#xff1…

想高效开发?从文件系统开始着手。。。

4G-Cat.1模组的文件系统关系着数据传输速度、存储效率&#xff0c;以及数据安全性等等诸多因素&#xff0c;在应用开发中是非常重要的。今天我们继续学习Air201的实用示例——文件系统的使用。 Air201文件系统的使用 合宙Air201资产定位模组——是一个集成超低功耗4G通信、语音…

密集行人数据集 CrowdHumanvoc和yolo两种格式,yolo可以直接使用train val test已经划分好有yolov8训练200轮模型

密集行人数据集 CrowdHuman voc和yolo两种格式&#xff0c;yolo可以直接使用 train val test已经划分好 有yolov8训练200轮模型。 CrowdHuman 密集行人检测数据集 数据集描述 CrowdHuman数据集是一个专为密集行人检测设计的数据集&#xff0c;旨在解决行人密集场景下的检测挑…

英伟达:AI时代的领跑者,引领智能计算的未来@附149页PDF文件下载

在人工智能的浪潮中&#xff0c;英伟达&#xff08;NVIDIA&#xff09;以其卓越的GPU技术&#xff0c;成为了这个时代的领跑者。从游戏显卡的霸主到AI计算的领导者&#xff0c;英伟达的转型之路充满了创新与突破。今天&#xff0c;我们将深入探讨2024年英伟达如何通过其战略布局…

RockTrack:A 3D Robust Multi-Camera-Ken Multi-Object Tracking Framework

RockTrack: A 3D Robust Multi-Camera-Ken Multi-Object Tracking Framework 基础信息 单位&#xff1a;哈尔滨理工大学论文&#xff1a;https://arxiv.org/pdf/2409.11749代码&#xff1a;https://github.com/lixiaoyu2000/Rock-Track (未全部放出)数据集&#xff1a;nuScen…

U-Boot的基本使用

直接参考【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81 在上一篇中我们学习了如何进行 I.MX6U 的裸机开发&#xff0c;通过 21 个裸机例程我们掌握了I.MX6U 的常用外设。通过裸机的学习我们掌握了外设的底层原理&#xff0c;这样在以后进行 Linux 驱动开发的时候就只需要将精…

典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式

先看代码与实现&#xff1a; 文件结构 triangle_area4.jsp <% page contentType"text/html;charsetUTF-8" pageEncoding"UTF-8" %> <html> <body> <%--<jsp:useBean>&#xff1a;用于在JSP中实例化JavaBean。在这里&#xff0c…

校医务室健康服务系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;医生管理&#xff0c;医患交流管理&#xff0c;预约医生管理&#xff0c;健康打卡管理&#xff0c;运动打卡管理&#xff0c;饮食打卡管理 微信端账号功能包括&#xff1a;系统首…

OpenAI o1模型背后的技术解析 | 后训练阶段的缩放法则与推理优化

引言 随着人工智能技术的迅速发展&#xff0c;大模型的能力提升成为各大研究机构的重点。2024年9月13日&#xff0c;OpenAI发布了o1系列模型&#xff0c;在数学、代码生成、长程规划等领域取得了显著提升。这些进展并非简单依靠大模型的参数扩展&#xff0c;而是基于强化学习以…

[Meachines] [Medium] Jeeves Jenkins-RCE+KeePass-Crack+Pass-the-Hash+(NTFS)ADS攻击

信息收集 IP AddressOpening Ports10.10.10.63TCP:80,135,445,50000 $ nmap -p- 10.10.10.63 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 80/tcp open http Microsoft IIS httpd 10.0 | http-methods: |_ Potentially risky methods:…

C++--C++11

1. C11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了 C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞 进行修复&#xff0c;语言的核心部分则没有改动&#xff0c;因此人们习惯性的把两个标准合…

口哨声、歌声、boing声和biotwang声:用AI识别鲸鱼叫声

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

fo-dicom开源库是如何满足 DICOM标准的基本要求

前言 前一篇文章&#xff0c;我们介绍了fo-dicom是一个怎样的开源库&#xff1a;fo-dicom&#xff0c;第一个基于.NET Standard 2.0 开发的DICOM开源库&#xff0c;在学会使用fo-dicom进行DICOM数据处理之前&#xff0c;需要先了解几个非常重要的概念&#xff1a; DICOM基本概…

用Qt 对接‌百度AI平台

很多同学想利用几大模型AI弄点东西&#xff0c;但又不知道如何去介入&#xff1f;&#xff1f;最近帮同学弄点东西&#xff0c;刚好要接入到AI平台&#xff0c;就顺便研究了一下&#xff0c;并记录下来。 首先我们选择的 AI模型是百度的&#xff0c;然后注册&#xff0c;申请密…

HX711电子秤模块详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 3.工作原理介绍 三、程序设计 main.c文件 hx711.h文件 hx711.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 HX711是一种高精度、低成本的压力传感器信号放大器&#xff0c;主要用于测量重力或压力变化。…

Allow anonymous access to my Azure OpenAI chat bot

题意&#xff1a;允许匿名访问我的 Azure OpenAI 聊天机器人 问题背景&#xff1a; I have an Azure OpenAI chat bot using my own data (I configured an OpenAI resource and chose Deploy as Web App) . Members of my domain can access it by logging in. Now I want it…

数据库基础知识---------------------------(3)

MYSQL的索引 用于快速找出在某个列中有一特定值的行&#xff0c;不使用索引&#xff0c;MySQL必须从第一条记录开始读完整个表&#xff0c;直到找出相关的行。按实现方式分为Hash索引和BTree索引 单列索引 普通索引 允许在定义索引的列中插入重复值和空值唯一索引 索引列的值必…

从黎巴嫩电子通信设备爆炸看如何防范网络电子袭击

引言&#xff1a; 在当今数字化时代&#xff0c;电子通信设备已成为我们日常生活中不可或缺的一部分。然而&#xff0c;近期黎巴嫩发生的电子设备爆炸事件提醒我们&#xff0c;这些设备也可能成为危险的武器。本文将深入探讨电子袭击的原理、防范措施&#xff0c;以及网络智能…