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.rhkb.cn/news/273509.html

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

相关文章

柚见第十期(后端队伍接口详细设计)

创建队伍 用户可以 创建 一个队伍&#xff0c;设置队伍的人数、队伍名称&#xff08;标题&#xff09;、描述、超时时间 P0 队长、剩余的人数 聊天&#xff1f; 公开 或 private 或加密 信息流中不展示已过期的队伍 请求参数是否为空&#xff1f;是否登录&#xff0c;未登录不…

基于pytest的证券清算系统功能测试工具开发

需求 1.造测试数据&#xff1a;根据测试需要&#xff0c;自动化构造各业务场景的中登清算数据与清算所需起来数据 2.测试清算系统功能&#xff1a; 自动化测试方案 工具设计 工具框架图 工具流程图 实现技术 python, pytest, allure, 多进程&#xff0c;mysql, 前端 效果 测…

MySQL 的基础操作

数据库的基础操作 1. 库操作2. 表的操作3. 数据类型 数据库是现代应用程序中至关重要的组成部分&#xff0c;通过数据库管理系统&#xff08;DBMS&#xff09;存储和管理数据。 1. 库操作 创建数据库 创建数据库是开始使用数据库的第一步。下面是一些常见的创建数据库的示例&a…

Python从0到100(三):Python中的变量介绍

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

Day6 java 常用API

文章目录 1、Calendar1.1 Calendar日历对象 2、JDK8 之后新增的时间类2.1 LocalDate、LocalTime 、LocalDateTime2.2 ZoneId 、ZoneIdTime2.3 Instant2.4 DateTimeFormatter2.5 Period2.6 Duration 1、Calendar 在了解calendar之前&#xff0c;先用SimpleDateFormat 写一个小例…

Linux快速入门学习-2 Linux安装环境准备

文章目录 Linux发展前景及就业形势Windows操作系统简介硬盘分区简介Linux安装环境准备Linux系统安装图解 Linux发展前景及就业形势 权威部门统计&#xff0c;未来几年内我国软件行业的从业机会十分庞大&#xff0c;中国每年对IT软件人才的需求将达到200万人左右。而对于Linux专…

Docker进阶:深入理解 Dockerfile

Docker进阶&#xff1a;深入理解 Dockerfile 一、Dockerfile 概述二、为什么要学习Dockerfile三、Dockerfile 编写规则四、Dockerfile 中常用的指令1、FROM2、LABEL3、RUN4、CMD5、ENTRYPOINT6、COPY7、ADD8、WORKDIR9、 ENV10、EXPOSE11、VOLUME12、USER13、注释14、ONBUILD 命…

Java面试题之JVM

JVM整体架构 堆 minor gc&#xff1a;回收年轻代的垃圾对象 full GC&#xff1a;回收整个堆的垃圾对象 当full GC也回收不了就会OOM STW运行时会停掉所有用户线程 JVM调优的真正目的就是为了减少STW的次数 为什么会有STW&#xff1a;避免非垃圾对象发生变化&#xff0c;暂…

【深度学习笔记】优化算法——Adam算法

Adam算法 &#x1f3f7;sec_adam 本章我们已经学习了许多有效优化的技术。 在本节讨论之前&#xff0c;我们先详细回顾一下这些技术&#xff1a; 在 :numref:sec_sgd中&#xff0c;我们学习了&#xff1a;随机梯度下降在解决优化问题时比梯度下降更有效。在 :numref:sec_min…

Linux-socket套接字

前言 在当今数字化时代&#xff0c;网络通信作为连接世界的桥梁&#xff0c;成为计算机科学领域中至关重要的一部分。理解网络编程是每一位程序员必备的技能之一&#xff0c;而掌握套接字编程则是深入了解网络通信的关键。本博客将深入讨论套接字编程中的基本概念、常见API以及…

GitOps实践之Argo CD (2)

argocd 【-1】argocd可以解决什么问题? helm 部署是手动的?依赖流水线。而有时候仅仅更新一个小东西,流水线跑好久,CD真的不应该和CI耦合。不同环境的helm配置不同,手动修改问题多,可以用git管理起来,例如分不同环境用目录区分。argocd创建应用可以不通环境部署到不同集…

Langchain-Chatchat本地搭建ChatGLM3模型和提取PDF内容

文章目录 1、软件要求2、安装CUDA2.1、安装gcc2.2、安装CUDA 3、安装Anaconda33.1、下载Anaconda33.2、创建python虚拟环境 4、部署系统4.1、下载源码4.2、安装依赖4.3、下载模型4.4、初始化配置和知识库4.4.1、初始化配置4.4.2、初始化知识库 4.5、运行4.6、运行4.6.1、启动4.…

C语言编译成库文件的要求

keil编译成库文件 在Keil中&#xff0c;将C语言源文件编译成库文件通常需要进行以下步骤&#xff1a; 创建一个新的Keil项目&#xff0c;并将所需的C语言源文件添加到该项目中。 在项目设置中配置编译选项&#xff0c;确保生成的目标文件符合库文件的标准格式。 编译项目&…

基于PHP的餐厅管理系统APP设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 MVC 3 1.2 ThinkPHP 3 1.3 MySQL数据库 3 1.4 uni-app 4 1.5 本章小结 4 2 系统分析 5 2.1 功能需求 5 2.2 用例分析 7 2.3 非功能需求 8 2.4 本章小结 8 3 系统设计 9 3.1 系统总体设计 9 3.2 系统详细设计 10 3.3 本章小…

基于Java+springboot+VUE+redis实现的前后端分类版网上商城项目

基于Java springbootVUEredis实现的前后端分类版网上商城项目 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言…

Ajax、Axios、Vue、Element与其案例

目录 一.Ajax 二.Axios 三.Vue 四.Element 五.增删改查案例 一.依赖&#xff1a;数据库&#xff0c;mybatis&#xff0c;servlet&#xff0c;json-对象转换器 二.资源&#xff1a;elementvueaxios 三.pojo 四.mapper.xml与mapper接口 五.service 六.servlet 七.html页…

css flex 布局换行

默认使用display: flex;是不换行的&#xff0c;只需要加上flex-wrap: wrap;就行了&#xff0c;效果图 .app-center {display: flex;flex-wrap: wrap;justify-content:flex-start; } 通过上面我们发现虽然时间换行了&#xff0c;但是每行的边距不一样 加上这个就行了&#xff…

微信小程序-分包

分包 1.什么是分包 分包指的是把一个完整的小程序项目&#xff0c;按照需求划分为不同的子包&#xff0c;在构建时打包成不同的分包&#xff0c;用户在使用时按需进行加载。 2.分包的好处 对小程序进行分包的好处主要有以下两点&#xff1a; 可以优化小程序首次启动的下载时间…

二维码图案样式怎么改?二维码改样式的简单方法

怎么修改二维码图案的样式呢&#xff1f;一般情况下生成的二维码图案大多是黑白的普通样式&#xff0c;那么很多人会为了提高展现效果或者增加辨识度&#xff0c;需要修改二维码的图案样式、添加logo、文字等其他内容&#xff0c;那么面对这样的需求该如何解决呢&#xff1f;下…

docker学习(十四)docker搭建私服

docker私服搭建&#xff0c;配置域名访问&#xff0c;设置访问密码 启动registry docker run -d \-p 5000:5000 \-v /opt/data/registry:/var/lib/registry \registrydocker pull hello-world docker tag hello-world 127.0.0.1:5000/hello-world docker push 127.0.0.1:5000…