Linux之线程控制

目录

一、POSIX线程库

二、线程的创建

三、线程等待

四、线程终止

五、分离线程

六、线程ID:pthread_t

1、获取线程ID

2、pthread_t

七、线程局部存储:__thread


一、POSIX线程库

由于Linux下的线程并没有独立特有的结构,所以Linux并没有提供线程相关的接口。

而我们所说的,pthread线程库是应用层的原生线程库。这个线程库并不是系统接口直接提供的,而是由第三方帮我们提供的。

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

二、线程的创建

pthread_create:其功能就是创建线程。

NAMEpthread_create - create a new threadSYNOPSIS#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);Compile and link with -pthread.

参数说明:

thread:获取创建成功的线程ID,该参数是一个输出型参数。

attr:用于设置创建线程的属性,传入nullptr表示使用默认属性。(我们一般不关心,直接设为nullptr)

start_routine:该参数是一个函数指针,表示线程启动后要执行的函数。

arg:传给线程执行函数的参数。

返回值:线程创建成功返回0,失败返回错误码。返回值也可以自己设置,返回给主线程。主线程通过pthread_join获取。

主线程:当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,这个线程就叫做主线程。

下面我们让主线程调用pthread_create函数创建一个新线程:

#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{cout << "new thread pid: " << getpid() << "\n"<< endl;sleep(20);return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread 1");while (true){cout << "main thread pid: " << getpid() << endl;sleep(1);}return 0;
}

 

使用ps -aL命令,可以显示当前的轻量级进程。

从上图,我们看到两个线程的PID相同,说明他们属于同一个进程。但是他们的LWP值不同,说明他们是两个不同的线程。LWP就是轻量级进程的ID。

注:在Linux中,线程与内核的LWP是一一对应的,实际上操作系统调度的时候是根据LWP调度的,而不是PID,只不过我们之前接触到的都是单线程进程,其PID和LWP是相等的,所以对于单线程进程来说,调度时采用PID和LWP是一样的。

我们也可以让一个主线程创建多个新线程

#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{string name = (char *)argc;while (true){cout << name << "---"<< "pid: " << getpid() << "\n"<< endl;sleep(1);}
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof(name), "%s-%d", "thread", i);pthread_create(tid + i, nullptr, thread_run, (void *)name);sleep(1);}while (true){cout << "main thread pid: " << getpid() << endl;sleep(3);}return 0;
}

因为主线程和五个新线程都属于同一个进程,所以它们的PID都是一样的。 

三、线程等待

一个线程被创建出来,那么这个线程就如同进程一般,也是需要被等待的。如果主线程不对新线程进行等待,那么这个新线程的资源也是不会被回收的。如果不等待会产生类似于“僵尸进程”的问题,也就会造成内存泄漏。所以线程需要被等待。

pthread_join:其功能就是进行线程等待

NAMEpthread_join - join with a terminated threadSYNOPSIS#include <pthread.h>int pthread_join(pthread_t thread, void **retval);Compile and link with -pthread.

参数说明:

thread:被等待线程的ID。
retval:线程退出时的退出码信息。

返回值:线程等待成功返回0,失败返回错误码。

#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{int count = 10;while (true){sleep(1);if (count++ == 10)break;}cout << "new thread  done ... quit" << endl;return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread1");pthread_join(tid, nullptr);cout << "main thread wait done ... quit" << endl;return 0;
}

第二个参数是用来获取新线程返回值的。主线程可以通过新线程的返回值拿到新线程的计算结果(该结果也可以保存在堆空间上)

include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{int count = 10;while (true){sleep(1);if (count++ == 10)break;}cout << "new thread  done ... quit" << endl;return (void *)10;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread1");void *ret = nullptr;pthread_join(tid, &ret);cout << "main thread wait done ... quit"<< " " << (long long)ret << endl;return 0;
}

四、线程终止

return:最简单的终止线程的方式,就是使用return返回一个返回值来终止线程。

pthread_exit:其功能就是终止一个线程。(终止线程不能使用exit,因为它是用来终止进程的)

参数,retval:设置退出结果。

NAMEpthread_exit - terminate calling threadSYNOPSIS#include <pthread.h>void pthread_exit(void *retval);Compile and link with -pthread.
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{int count = 10;while (true){sleep(1);if (count++ == 10)break;}cout << "new thread  done ... quit" << endl;pthread_exit((void*)17);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread1");void *ret = nullptr;pthread_join(tid, &ret);cout << "main thread wait done ... quit"<< " " << (long long)ret << endl;return 0;
}

 

pthread_cancel:其功能是取消一个线程。

参数,thread:线程ID。

NAMEpthread_cancel - send a cancellation request to a threadSYNOPSIS#include <pthread.h>int pthread_cancel(pthread_t thread);Compile and link with -pthread.
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{string name = (char *)argc;int count = 10;while (true){sleep(1);if (count++ == 10)break;}cout << "new thread  done ... quit" << endl;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread1");void *ret = nullptr;pthread_cancel(tid);pthread_join(tid, &ret);cout << "main thread wait done ... quit"<< " " << (long long)ret << endl;return 0;
}

 

线程被取消,线程等待时获取的退出码为-1。 

五、分离线程

新线程退出后,主线程需要对其进行pthread_join操作,否则无法释放资源,从而造成内存泄漏。
但如果主线程不关心新线程的返回值,此时我们可以将该新线程进行分离,后续当新线程退出时就会自动释放线程资源。

一个线程如果被分离了,这个线程依旧要使用该进程的资源,依旧在该进程内运行,甚至这个线程崩溃了一定会影响其他线程,只不过这个线程退出时不再需要主线程去join了,当这个线程退出时系统会自动回收该线程所对应的资源。

pthread_detach:其功能就是进行分离线程。一般是线程自己分离。

int pthread_detach(pthread_t thread);

参数说明:thread:被分离线程的ID。

返回值说明:

线程分离成功返回0,失败返回错误码。

#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{pthread_detach(pthread_self());int count = 10;while (true){sleep(1);if (count++ == 10)break;}cout << "new thread  done ... quit" << endl;pthread_exit((void*)17);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread1");void *ret = nullptr;cout << "main thread wait done ... quit"<< " " << (long long)ret << endl;return 0;
}

 如果我们在线程分离了之后,任然等待,会怎么样呢?

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{pthread_detach(pthread_self());int count = 9;while (true){sleep(1);if (count++ == 10)break;}cout << "new thread  done ... quit" << endl;pthread_exit((void *)17);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread1");sleep(2);int n = pthread_join(tid, nullptr);cout << "n: " << n << "errstring: " << strerror(n) << endl;return 0;
}

六、线程ID:pthread_t

pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中,该线程ID和内核中的LWP是完全不一样的。内核中的LWP属于进程调度的范畴,需要一个数值来唯一表示该线程。

那么pthread_t到底是什么类型呢?

1、获取线程ID

pthread_self:获取线程的ID。

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <pthread.h>using namespace std;void *thread_run(void *argc)
{int count = 9;while (true){sleep(1);if (count++ == 10)break;}cout << "new thread  done ... quit"<< "new thread ID: " << pthread_self() << endl;pthread_exit((void *)17);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void *)"thread1");void *ret = nullptr;sleep(2);cout << "main thread ID: " << pthread_self() << endl;pthread_join(tid, &ret);return 0;
}

为什么线程的ID数值这么大呢?下面我们就来讲一讲。 

2、pthread_t

进程运行时线程动态库被加载到内存,然后通过页表映射到进程地址空间中的共享区,此时该进程内的所有线程都是能看到这个动态库的。

其中主线程采用的栈是进程地址空间中原生的栈,而其余线程采用的栈就是由线程库帮我们在共享区中开辟的。

线程库给每个新线程提供属于自己的struct pthread,当中包含了对应线程的各种属性;每个线程还有自己的线程局部存储,当中包含了对应线程被切换时的上下文数据。其中,还有线程栈。如下图:

所以,线程ID本质就是进程地址空间共享区上对应的struct pthread的虚拟地址。 

七、线程局部存储:__thread

假设有一个全局变量:g_val。我们知道,各个线程是共享全局变量的。不同的线程可以对同一个全局变量进行操作。那么如果我们想让每个线程都拥有属于自己的g_val,那么我们可以加上关键字:__thread。这种现象就叫做线程局部存储。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

白炽灯护眼还是LED护眼?热门护眼台灯全方位实测推荐

白炽灯是制热发光体&#xff0c;电流通过发热时产生热量&#xff0c;灯丝温度达到2000摄氏度以上&#xff0c;灯丝处于白炽状态了&#xff0c;它的光电转换效率低&#xff0c;费电&#xff0c;寿命也比较短&#xff0c;最主要的一点是光线会比较集中刺眼&#xff0c;而且调节性…

Cassandra 安装部署

文章目录 一、概述1.官方文档2. 克隆服务器3.安装准备3.1.安装 JDK 113.2.安装 Python3.3.下载文件 二、安装部署1.配置 Cassandra2.启动 Cassandra3.关闭Cassandra4.查看状态5.客户端连接服务器6.服务运行脚本 开源中间件 # Cassandrahttps://iothub.org.cn/docs/middleware/…

深入理解Java多线程与线程池:提升程序性能的利器

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 引言 一、实现多线程 1.1. 继承Thread类 1.2. 实现Runnab…

趣学前端 | Taro迁移完成之后,总结了一些踩坑经验

背景 四月份的时候&#xff0c;尝试将老的移动端项目改造成多端。因为老项目使用的React框架&#xff0c;综合考量&#xff0c;保障当前业务开发的进度同时&#xff0c;进行项目迁移&#xff0c;所以最后选择了Taro框架。迁移成本会低一些&#xff0c;上手快一些。 上个月&am…

js之原型链

在JavaScript中&#xff0c;原型链是一种用于实现继承和属性查找的机制。每个对象都有一个内部属性[[Prototype]]&#xff0c;这个属性指向创建该对象时使用的构造函数的“prototype"属性。对象的方法和属性定义在它的原型对象上。 1.原型&#xff08;Prototypes&#xf…

AIGC实战——GPT(Generative Pre-trained Transformer)

AIGC实战——GPT 0. 前言1. GPT 简介2. 葡萄酒评论数据集3. 注意力机制3.1 查询、键和值3.2 多头注意力3.3 因果掩码 4. Transformer4.1 Transformer 块4.2 位置编码 5. 训练GPT6. GPT 分析6.1 生成文本6.2 注意力分数 小结系列链接 0. 前言 注意力机制能够用于构建先进的文本…

mysql中 多表查询介绍

在 MySQL 中&#xff0c;多表查询是 SQL 语句的重要组成部分&#xff0c;用于从两个或多个表中检索数据。多表查询可以帮助我们更灵活地处理复杂的数据关系&#xff0c;并从中获取所需的信息。以下是 MySQL 中常见的多表查询及其特点、区别和应用场景。 常见多表查询 1. **内连…

java中几种对象存储(文件存储)中间件的介绍

一、前言 在博主得到系统中使用的对象存储主要有OSS&#xff08;阿里云的对象存储&#xff09; COS&#xff08;腾讯云的对象存储&#xff09;OBS&#xff08;华为云的对象存储&#xff09;还有就是MinIO 这些玩意。其实这种东西大差不差&#xff0c;几乎实现方式都是一样&…

【JAVA】CSS2:样式、选择器、伪类、颜色、字体、边框、列表、背景、盒子、布局、浮动

本文介绍了CSS样式、选择器、伪类、像素、颜色、字体、边框、列表、表格属性、背景、盒子、布局与浮动 1.样式 1.1 行内样式 <h1 style"color: aqua;font-size: large;">123</h1> 1.2 内部样式 <style>h1{color: red;font: 100;}</style>…

从16-bit 到 1.58-bit :大模型内存效率和准确性之间的最佳权衡

通过量化可以减少大型语言模型的大小&#xff0c;但是量化是不准确的&#xff0c;因为它在过程中丢失了信息。通常较大的llm可以在精度损失很小的情况下量化到较低的精度&#xff0c;而较小的llm则很难精确量化。 什么时候使用一个小的LLM比量化一个大的LLM更好? 在本文中&a…

关于比特币的AI对话

【ChatGPT】 比特币源码开源吗&#xff1f; 是的&#xff0c;比特币的源码是开源的。比特币项目是在MIT许可证下发布的&#xff0c;这意味着任何人都可以查看、修改、贡献和分发代码。比特币的源码托管在GitHub上&#xff0c;可以通过下面的链接进行访问&#xff1a; https://g…

13.【蓝桥杯】-ing 移除链表元素 简单题

一、题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5]示例 2&#xff1a; 输入&…