Linux_线程的使用

目录

1、线程与进程的关系 

2、线程的优缺点

3、创建线程

4、查看启动的线程

5、验证线程是共享地址空间的

6、pthread_create的重要形参

6.1 线程id 

6.2 线程实参

7、线程等待  

8、线程退出

9、线程取消 

10、线程tcb 

10.1 线程栈

11、创建多线程 

12、__thread 

13、线程分离 

结语 


前言:

        线程是操作系统进行调度的基本单位,他属于进程的子集。在Linux下,通过实现轻量化进程来实现线程,因此线程具有进程的相关特性,比如线程必须有自己的代码资源,有属于自己独立的数据空间,并且同一个进程下的线程所看到的地址空间是属于该进程的,因为创建线程实际上就是在该进程下创建task_struct结构体(该结构体的作用是方便操作系统对该执行流的调度),这些task_struct结构体跟进程共用空间资源,只不过线程可以在单一进程执行流的基础上实现多执行流并发式的运行代码,以至于提高cpu的效率。

1、线程与进程的关系 

         说到线程就离不开进程的概念,因为线程是在进程的基础上实现的,多线程是底层就是创建了多个task_struct结构体作为进程的执行分支,但是他们依然是共用进程的数据资源,线程示意图如下:

        当系统里创建一个进程,则系统需要给该进程分配新的地址空间、页表、物理内存等等空间资源,所以说进程是系统分配资源的实体。但是当系统里有了新的线程则不会给线程分配新的空间资源,而是给让线程使用进程的空间资源。


        线程的独立部分:     

        线程虽然和进程共用地址空间,但是线程也有自己独立的部分,比如:线程ID, 保存上下文的寄存器,线程栈,errno,block信号集,调度优先级。

2、线程的优缺点

        线程的优点:

        1、当我们需要并发执行代码时,创建一个线程的工作比创建一个进程的工作要小得多。

        2、当cpu切换PCB时,切换线程的效率比切换进程的效率略高。

        3、线程所占用的资源小于进程。

        4、线程之间的通信代价比进程的要小。

        5、提高程序的并发性。

        线程的缺点:

        1、若单个线程收到信号退出,则会导致整个进程都退出。

        2、多线程访问共同资源时是不受保护的,会导致意料之外的错误。

        3、编写多线程的难度很高。 

        4、若单个线程因为异常崩溃,则会导致整个进程崩溃。

3、创建线程

        创建线程需要用到的接口如下:

#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
//thread是一个执行类型为pthread_t的变量的指针
//attr是一个指针,他指向的结构体包含新线程的各种属性,设为nullptr则表示采用默认属性
//start_routine是新线程要执行的函数,他接收一个void*,返回值一个void*
//arg表示新线程要执行的函数的实参

         创建线程前需要先定义一个类型为pthread_t的变量作为实参传递给函数pthread_create,该变量的作用是让用户可以通过他找到对应的线程,创建线程的代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void *threadRun(void* args)
{while(1){cout << "子线程: " << getpid() << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;//先定义一个pthread_t类型的变量//该函数调用完成后会赋予tid新的值,表示该线程的idpthread_create(&tid, nullptr, threadRun, nullptr);while(1){cout << "主线程: " << getpid() << endl;sleep(1);}
}

        测试结果:

        注意:使用线程的接口时要在编译的时候要手动链接pthread库,如下图:

4、查看启动的线程

        在Linux下,使用指令:ps -aL,就可以查看用户启动的线程了。如下图: 

        LWP表示轻量级进程的pid,即线程的pid,LWP是给系统调度线程专门设置的标识符。 

5、验证线程是共享地址空间的

        定义一个全局变量,若所有线程只能看到唯一一份全局变量,那么就可以证明进程下的所有线程用的是同一个地址空间,代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;int g_val = 10;
// 新线程
void *threadRoutine(void *args)
{while (true){printf("子线程 pid: %d, g_val: %d, \&g_val: 0x%p\n", getpid(), g_val, &g_val);sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr); while (true){printf("主线程 pid: %d, g_val: %d,\&g_val: 0x%p,\n", getpid(), g_val, &g_val);sleep(1);g_val++;}return 0;
}

         运行结果:

        从结果可以看到,哪怕主线程对全局变量进行更改,其他线程拿到的值是更改后的值,若是父子进程关系,则另一方会发生写时拷贝,线程之间没有这么做,说明线程是共享地址空间的。 

6、pthread_create的重要形参

6.1 线程id 

        线程有两个标识符,一个是LWP,是给系统看的,另一个是线程id,是给用户看的。线程id就是创建线程时定义的pthread_t类型的变量,该变量作为pthread_create的输出型参数,在调用完pthread_create后该变量保存的就是线程id了。

        查看线程id的代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;int g_val = 10;
// 新线程
void *threadRoutine(void *args)
{
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr); while (true){printf("主线程 pid: %d, g_val: %d,\&g_val: 0x%p,子线程id:%p \n", getpid(), g_val, &g_val,tid);sleep(1);g_val++;}return 0;
}

        运行结果:

        从测试结果发现,线程id实际上就是一串地址,这个地址就是线程在地址空间内的映射,间接说明了线程的管理是在用户空间内的进行,并不是由操作系统像管理进程PCB一般在内核空间进行,具体看下文线程tcb。

6.2 线程实参

        线程的任务就是执行pthread_create的函数指针,并且该函数具有一个void*的形参,那么如何使用该void*的形参呢?

        测试代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;// 新线程
void *threadRoutine(void *args)
{char *name = static_cast<char *>(args);//需要强转while (true){cout << name << endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"线程1");//需要强转while (true){}return 0;
}

         运行结果:

7、线程等待  

        子线程退出时主线程也要对其进行等待,等待的原因和父子进程一样,防止内存泄漏和回收退出信息,若不等待线程,则线程的tast_struct会一直存在,会造成不必要的资源浪费,进行线程等待的接口介绍如下:

#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
//thread表示要等待的线程id
//retval是一个二级指针,是一个输出型参数,目的是拿到线程返回的void*

         线程等待的测试代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;// 新线程
void *threadRoutine(void *args)
{int count = 5;while (count){count--;sleep(1);}return (void*)1;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);void* retval;pthread_join(tid,&retval);//等待子线程cout<<"线程等待成功:"<<(long long)retval<<endl;return 0;
}

        运行结果:

        由于线程执行的函数虽然可以返回一个void*的变量,但是我们却没有办法接收该返回值,因为这不是一个简单的函数调用。所以只能通过调用pthread_join,然后传递一个二级指针给他,pthread_join就可以通过输出型参数把void*变量给带出来。

8、线程退出

        进程退出常常用exit函数,只要一个进程调用了exit函数,则该进程就直接结束了。但是若想仅仅退出一个线程,则不能用exit,因为当一个线程用exit退出,就会把整个进程退出。线程退出有专门的退出函数,该函数介绍如下:

#include <pthread.h>void pthread_exit(void *retval);
//该函数会退出当前线程,并且返回一个void*变量

         线程退出测试代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;// 新线程
void *threadRoutine(void *args)
{int count = 5;while (count){count--;sleep(1);}//return (void*)1;pthread_exit((void*)100);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);void* retval;pthread_join(tid,&retval);cout<<"线程等待成功:"<<(long long)retval<<endl;return 0;
}

        运行结果:

9、线程取消 

        可以调用函数pthread_cancel可以在线程退出前取消该线程,该函数介绍如下:

#include <pthread.h>int pthread_cancel(pthread_t thread);
//thread表示要取消的线程

         线程取消测试代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;// 新线程
void *threadRoutine(void *args)
{int count = 5;while (count){count--;sleep(1);}pthread_exit((void*)100);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);pthread_cancel(tid);void* retval;pthread_join(tid,&retval);cout<<"线程等待成功:"<<(long long)retval<<endl;return 0;
}

        运行结果:

        如果一个线程被取消,则该线程的退出码为-1。 

10、线程tcb 

         上文讲到线程id是给用户看的,通过上述代码打印线程id,可以观察到线程id实际上就是一串虚拟地址,线程id的值如下:

        分析id的地址,可以发现该地址数属于进程地址空间的共享区内,说明线程本身就是存储在用户空间内的,但是我们自己并没有对线程做任何管理,那么线程是如何被管理的呢?可以从函数pthread_create得知,当我们调用pthread_create后,该函数就会返回一个线程id给到我们,说明该函数内部会自己维护线程,而该函数的实现是存储在线程库(pthread.so)里的,而线程库会在程序运行起来时加载到内存并映射在共享区内,所有可以得出一个结论:线程由线程库维护,并映射在地址空间的共享区内


        具体示意图如下:

        从上图可以发现,tcb就是管理线程的结构体,线程库以维护tcb从而维护线程,而tid就是线程id,他就是tcb的首地址,这也就很好的解释了为什么可以通过线程id找到对应的线程了,这个地址是线程库为用户申请,也就是用户调用函数pthread_create后所得到的地址。并且不同的进程创建的线程都会被线程库在内存中统一管理,只是这些线程会分别映射到他们的进程共享区中

10.1 线程栈

         从上图可以发现除了地址空间的栈空间外,线程tcb中也维护一个名为线程栈的空间,而线程栈是采用数组的方式模拟出来的,这些模拟栈被保存在共享区,由线程库来维护。栈与栈之间相互独立,不可直接访问,但是同一进程下的其他线程采用特别的方式也可以访问到对方的栈,因为毕竟都在同一个地址空间内。

11、创建多线程 

        创建多线程的思路:利用循环定义多个线程id,但是由于新的循环会覆盖旧的线程id,因此需要把每个线程id存入容器中,方便后续等待线程时能够找到他们的线程id,创建多线程代码如下:

#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>using namespace std;
#define NUM 4
struct threadData//给每个线程做标记
{string threadname;
};// 所有的线程,执行的都是这个函数?
void *threadRoutine(void *args)
{threadData *td = static_cast<threadData *>(args);int i = 5;while (i){cout << "我是" << td->threadname << ", pid: " << getpid() << endl;sleep(1);i--;}delete td;return nullptr;
}void InitThreadData(threadData *td, int number)
{td->threadname = "线程-" + to_string(number);
}int main()
{// 创建多线程!vector<pthread_t> tids;for (int i = 0; i < NUM; i++){pthread_t tid;threadData *td = new threadData;InitThreadData(td, i);//标记线程pthread_create(&tid, nullptr, threadRoutine, td);tids.push_back(tid);}//线程等待  for (int i = 0; i < tids.size(); i++){pthread_join(tids[i], nullptr);}return 0;
}

         运行结果:

12、__thread 

         __thread只能修饰内置类型,不能修饰自定义类型,他修饰的变量对于所有线程是可见的,有点类似全局变量,但是他跟全局变量的区别在于:__thread修饰的变量对于每个线程而言是独立的,换句话说,线程对该变量的修改不会影响其他线程所看到的值。

        测试代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;__thread int a = 10;void *threadRun(void* args)
{while(1){cout << "子线程: " << getpid() <<" a的值:"<<a++<<endl;//线程对a进行++sleep(1);}return nullptr;
}int main()
{pthread_t tid;//先定义一个pthread_t类型的变量//该函数调用完成后会赋予tid新的值,表示该线程的idpthread_create(&tid, nullptr, threadRun, nullptr);while(1){cout << "主线程: " << getpid() <<" a的值:"<<a<< endl;//此处a的值还是10吗?sleep(1);}
}

        运行结果:

        从结果可以看到,主线程的a是独立于子线程的a,原因就是a作为全局变量被__thread修饰了,因此所有线程都有一份独立的a。 

13、线程分离 

        主线程创建的子线程退出后,主线程若想拿到子进程退出的void*返回值,则主线程要对其进行join等待操作,但是若主线程不关心其返回值,则就没必要进行等待,因为等待也是一种负担。因此在这种情况下,可以让该子线程自行分离,即分离的子线程在退出后会自动释放空间资源。

        分离的函数介绍如下:

#include <pthread.h>int pthread_detach(pthread_t thread);
//thread表示要分离的线程

         线程分离测试代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstring>using namespace std;void *threadRun(void* args)
{pthread_detach(pthread_self());//pthread_self返回该线程的idint count = 3;while(count--){cout << "子线程: " << getpid() <<endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, nullptr);sleep(1);//要保证pthread_detach在pthread_join之前触发int n = pthread_join(tid, nullptr);printf("n = %d, who = 0x%x, why: %s\n", n, tid, strerror(n));
}

        测试结果:

        n = 22表示等待失败了,说明这些线程已经被分离了。 

结语 

         以上就是关于使用线程的讲解,线程是核心思想是创建多个执行流让程序实现并行运行,目的就是提高程序执行的效率,本文主要讲述如何创建线程和使用线程,包括线程的基本用法和概念,线程本身涉及的知识非常广,细节也特别多,在复杂的多线程下往往要考虑更多的东西。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

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

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

相关文章

VS2019安装MFC组件

VS2019支持的MFC版本是mfc140 ~ mfc142版本&#xff0c;它兼容VS2015、VS2017之前的老版本程序。 一、MFC的历史版本 MFC的历史版本如下&#xff1a; IDE发布时间工具集版本MSC_VERMSVCMFC版本dllVisual C6.01998V601200MSVC6.06.0mfc42.dll、mfcce400.dllVisual Studio 2002…

如何设计数据中心100G网络光纤布线

随着全球企业对带宽的需求呈指数级增长&#xff0c;数据中心需要升级以增强其计算、存储和网络能力。数据中心从10G/25G向100G迁移成为必然趋势。随着网络升级&#xff0c;数据中心的光纤布线系统也需要随之优化。本文将指导您如何设计数据中心100G网络光纤布线。 100G以太网的…

python-快速上手爬虫

目录 前言 爬虫需谨慎&#xff0c;切勿从入门到入狱&#xff01; 一点小小的准备工作 直接上手爬取网页 1.获取UA伪装 2.获取url 3.发送请求 4.获取数据并保存 总结 前言 爬虫需谨慎&#xff0c;切勿从入门到入狱&#xff01; 一点小小的准备工作 对pip进行换源&#xf…

基于微信小程序图书馆座位预约系统设计与实现

链接: 文档和工程文件地址: ** 2、未完待续:请到目标位置下载 ** 链接: 文档和工程文件地址:

分布式搜索之Elasticsearch入门

Elasticsearch 是什么 Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎&#xff0c;能够解决不断涌现出的各种用例。作为 Elastic Stack 的核心&#xff0c;它集中存储您的数据&#xff0c;帮助您发现意料之中以及意料之外的情况。 Elastic Stack 又是什么呢&a…

排序系列 之 快速排序

&#xff01;&#xff01;&#xff01;排序仅针对于数组哦本次排序是按照升序来的哦代码后边有图解哦 介绍 快速排序英文名为Quick Sort 基本思路 快速排序采用的是分治思想&#xff0c;即在一个无序的序列中选取一个任意的基准元素base&#xff0c;利用base将待排序的序列分…

【吊打面试官系列-ZooKeeper面试题】分布式集群中为什么会有 Master?

大家好&#xff0c;我是锋哥。今天分享关于 【分布式集群中为什么会有 Master&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 分布式集群中为什么会有 Master&#xff1f; 在分布式环境中&#xff0c;有些业务逻辑只需要集群中的某一台机器进行执行&#xff0c…

Leetcode1305.两颗二叉搜索树中的所有元素

1.题目要求: 给你 root1 和 root2 这两棵二叉搜索树。请你返回一个列表&#xff0c;其中包含 两棵树 中的所有整数并按 升序 排序。.2.思路: 我这个方法采用的是设立一个数组&#xff0c;然后用前序遍历把值存入数组中&#xff0c;然后用qsort给它排序 3.代码: /*** Definiti…

[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-21 VTC视频时序控制器设计

软件版本&#xff1a;Anlogic -TD5.9.1-DR1_ES1.1 操作系统&#xff1a;WIN10 64bit 硬件平台&#xff1a;适用安路(Anlogic)FPGA 实验平台&#xff1a;米联客-MLK-L1-CZ06-DR1M90G开发板 板卡获取平台&#xff1a;https://milianke.tmall.com/ 登录“米联客”FPGA社区 ht…

蚂蚁集团推出EchoMimic:能通过音频和面部标志生成逼真的肖像动画视频

蚂蚁集团最近推出了一项名为EchoMimic的新技术。能通过音频和面部标志生成逼真的肖像动画视频&#xff0c;让你的声音和面部动作被完美复制到视频中&#xff0c;效果自然如照镜子。 EchoMimic不仅可以单独使用音频或面部标志点生成肖像视频&#xff0c;也可以将两者结合&#…

任意空间平面点云旋转投影至水平面—罗德里格旋转公式

1、背景介绍 将三维空间中位于任意平面上的点云数据&#xff0c;通过一系列的坐标变换&#xff08;平移旋转&#xff09;&#xff0c;使其投影到XOY平面上&#xff0c;同时保证点云的几何中心与XOY平面的原点重合&#xff0c;同时点云形状保持不变。具体效果如下&#xff0c;具…

深入探究理解大型语言模型参数和内存需求

概述 大型语言模型 取得了显著进步。GPT-4、谷歌的 Gemini 和 Claude 3 等模型在功能和应用方面树立了新标准。这些模型不仅增强了文本生成和翻译&#xff0c;还在多模态处理方面开辟了新天地&#xff0c;将文本、图像、音频和视频输入结合起来&#xff0c;提供更全面的 AI 解…

MySQL MVCC原理

全称Multi-Version Concurrency Control&#xff0c;即多版本并发控制&#xff0c;主要是为了提高数据库的并发性能。 1、版本链 对于使用InnoDB存储引擎的表来说&#xff0c;它的聚簇索引记录中都包含两个必要的隐藏列&#xff1a; 1、trx_id&#xff1a;每次一个事务对某条…

Harbor系列之1:介绍、架构及工作流程说明

Harbor介绍、架构及工作流程说明 Harbor 是一个用于存储、签名和扫描内容的企业级容器镜像注册表项目。由 VMware 开发并于 2016 年开源。Harbor 提供了一些关键特性&#xff0c;使其成为企业使用的理想选择。 1. Harbor 介绍 1.1 什么是 Harbor Harbor 是一个开源的云原生…

UDP网口(1)概述

文章目录 1.计算机网络知识在互联网中的应用2.认识FPGA实现UDP网口通信3.FPGA实现UDP网口通信的方案4.FPGA实现UDP网口文章安排5.传送门 1.计算机网络知识在互联网中的应用 以在浏览器中输入淘宝网为例&#xff0c;介绍数据在互联网是如何传输的。我们将要发送的数据包称作A&a…

在 ROS 2 中创建一个节点的过程

在 ROS 2 中创建一个节点的过程包括几个关键步骤。以下是一般的步骤流程&#xff0c;使用 C 和 ament_cmake 构建系统为例&#xff1a; 步骤 1: 创建工作空间 如果还没有工作空间&#xff0c;首先创建一个&#xff1a; mkdir -p ~/my_ros2_ws/src cd ~/my_ros2_ws colcon bu…

Java学习Day10:总结帖

学习第十天&#xff0c;发一个总结帖&#xff01; 1.基本数据类型&#xff0c;变量 基本数据类型不用过多赘述&#xff0c;其在后面不论是面型对象还有其他知识等都会经常使用&#xff1b; 变量最重要的就是其定义&#xff1a; 这对于我们之后理解自定义类型变量有很大的用处…

【从零开始实现stm32无刷电机FOC】【实践】【5/7 stm32 adc外设的高级用法】

目录 采样时刻触发采样同步采样 点击查看本文开源的完整FOC工程 本节介绍的adc外设高级用法用于电机电流控制。 从前面几节可知&#xff0c;电机力矩来自于转子的q轴受磁力&#xff0c;而磁场强度与电流成正比&#xff0c;也就是说电机力矩与q轴电流成正相关&#xff0c;控制了…

通信网络机房服务器搬迁流程方案

数据中心机房搬迁是一项负责高难度的工程。整个搬迁过程充满挑战&#xff0c;伴随着各种风险。如何顺利的完成服务器的迁移&#xff0c;需要专业的数据中心服务商全程提供保障。友力科技&#xff08;广州&#xff09;有限公司&#xff0c;作为华南地区主流的数据中心服务商&…

Leetcode3208. 交替组 II

Every day a Leetcode 题目来源&#xff1a;3208. 交替组 II 解法1&#xff1a;环形数组 把数组复制一份拼接起来&#xff0c;和 3101 题一样&#xff0c;遍历数组的同时&#xff0c;维护以 i 为右端点的交替子数组的长度 cnt。 如果 i ≥ n 且 cnt ≥ k&#xff0c;那么 i…