Linux多线程

在这里插入图片描述

文章目录

  • 多线程
    • 多线程概念
    • 多线程优点
    • 多线程缺点
    • 线程和进程
  • Linux线程控制
    • POSIX线程库
    • 线程的创建
    • 进程ID获取
    • 线程终止
    • 线程等待
    • 线程分离
  • 总结

多线程

多线程概念

在Linux中,线程是进程内的执行单元。换句话说,线程是进程内部的子任务,它们共享相同的进程资源,如内存空间、文件描述符等。线程在进程内部运行,本质就是在进程地址空间内运行。并且每个线程都有自己的执行路径和栈,但它们可以访问相同的全局变量和数据结构。

在这里插入图片描述

多线程优点

多线程允许程序同时执行多个任务,从而提高了系统的响应速度和吞吐量,对于处理大量并发请求或任务非常有用。并且线程之间可以共享相同的内存地址空间,因此它们可以更容易地共享数据和通信,而不需要复杂的进程间通信机制。与多个独立进程相比,线程之间的切换成本较低,因为它们共享相同的上下文,使得创建和销毁线程相对较快。相对于进程间通信,线程之间的通信和同步通常更容易实现,因为它们共享相同的地址空间,简化了编程工作。多线程在多核处理器上还可以更有效地利用多个处理核心,从而提高性能。多线程也可以用于创建具有更快响应时间的应用程序,例如图形用户界面(GUI)应用程序,以便用户可以同时执行多个操作。

优点:

  1. 并发性
  2. 资源共享
  3. 资源节省
  4. 简化编程
  5. 更好的利用多核处理器
  6. 响应性
  7. 多任务处理

多线程在计算机编程中还有许多优点,这些优点使其成为处理并发任务的强大工具。

多线程缺点

凡事都有两面性,说完优点再来盘一下多线程的缺点。由于多线程共享相同的内存空间,因此可能会导致竞态条件,即多个线程同时尝试访问和修改共享数据,导致不可预测的结果和错误。还有当多个线程互相等待彼此释放资源时,可能会发生死锁,导致所有线程被阻塞,无法继续执行。每个线程都有自己的栈和上下文切换开销,创建大量线程可能会消耗大量系统资源,导致性能下降。并且多线程编程通常比单线程编程更复杂,需要处理同步、互斥、线程安全等问题,容易出现错误。这些多线程应用程序中的错误可能会更难以调试和重现,因为问题可能是由于特定的线程交互导致的,而这些交互可能是不确定的。其中多线程应用程序中的线程执行顺序和时间是不确定的,这也使得难以预测和控制应用程序的行为。虽然多线程可以在多核处理器上提高性能,但过多的线程可能会导致线程切换开销增加,从而降低性能。多线程应用程序可能容易受到一些安全漏洞,如数据泄漏、竞态条件漏洞等的影响。

缺点:

  1. 竞态条件
  2. 死锁
  3. 资源消耗
  4. 复杂性
  5. 调试困难
  6. 不确定性
  7. 性能下降
  8. 安全性问题

所以说要充分利用多线程的优势并避免其缺点,需要谨慎设计和测试多线程应用程序,并使用适当的同步机制来管理线程间的资源访问。此外,使用并发编程框架和工具可以帮助减轻一些多线程编程的挑战。

线程和进程

在这里插入图片描述

我们都知道进程是独立的执行单元,拥有独立的内存空间、文件描述符、系统资源等。每个进程都是一个独立的程序实例。而线程是进程内的执行单元,共享相同的内存空间、文件描述符和其他进程资源。可以说线程是进程的子任务,也被称为轻量级进程。因此进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。虽说线程共享进程数据,但是他们也拥有自己的一部分数据,例如线程ID,一组寄存器,栈,errno,信号屏蔽字,调度优先级。

Linux线程控制

在Linux中要进行线程控制就需要用到pthread库来实现线程控制。pthread库提供了创建、销毁、等待和管理线程的函数。而pthread是POSIX线程库的一部分,它为多线程编程提供了一组标准化的接口和工具。要使用这些函数库,要通过引入头文<pthread.h>,并且链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

POSIX线程库

POSIX线程库,通常称为pthread库,是一种用于多线程编程的标准库。POSIX是Portable Operating System Interface的缩写,它定义了一套API(应用程序编程接口),用于提高在不同操作系统上移植应用程序的可移植性。一些关键的pthread库功能包括:

  • 线程创建和管理:pthread库允许程序员创建、销毁和管理线程。使用pthread库,你可以在应用程序中创建多个线程,以实现并发执行。

  • 线程同步:提供了互斥锁、条件变量等同步机制,用于控制线程对共享资源的访问,防止竞态条件和确保数据一致性。

  • 线程间通信:支持线程间的通信机制,如信号量、消息队列等,以便线程之间能够进行有效的信息交换。

  • 线程属性:允许设置和获取线程的属性,如优先级、栈大小等。

  • 线程取消:提供了线程取消机制,使得一个线程能够取消另一个线程的执行。

  • 线程安全函数:POSIX线程库定义了一组线程安全的C标准库函数,这些函数可在多线程环境中安全使用。

在许多类Unix系统和类Unix系统兼容的操作系统上,包括Linux,pthread库是广泛使用的。开发者可以使用这个库在不同的平台上实现可移植的多线程应用程序。在使用pthread库时,通常需要在编译时链接libpthread库。

在这里插入图片描述

线程的创建

在Linux中可以用pthread_create函数来创建线程,他是一个 POSIX 线程库中的函数。它的函数原型如下:

在这里插入图片描述

其中它的参数 thread 是一个指向 pthread_t 类型的指针,用于存储新线程的标识符。attr 参数是一个指向 pthread_attr_t 类型的指针,用于设置线程的属性,通常可以传入 NULL 以使用默认属性。start_routine 参数是一个指向函数的指针,这个函数是新线程要执行的函数,它的参数和返回值都是 void *。arg 参数是一个传递给 start_routine 函数的参数。

其中pthread_t类型是一个无符号长整型,用于声明线程ID。
在这里插入图片描述
pthread_attr_t是一个用于线程属性的struct,它定义了线程的一些属性,例如线程的调度策略、堆栈大小、优先级等。
在这里插入图片描述

pthread_create 函数创建一个新线程,新线程会执行 start_routine 函数,并将 arg 作为参数传递给它。线程的标识符会存储在 thread 指向的位置。如果成功创建线程,pthread_create 返回0,否则返回一个非零的错误码,可以使用 pthread_strerror 函数来获取错误信息。

void *thread_run(void *args)
{for (int i = 0; i < 5; i++){cout << "run....." << i + 1 << endl;sleep(1);}cout << "线程退出......" << endl;return nullptr;
}int main()
{pthread_t t1;pthread_create(&t1, nullptr, thread_run, nullptr);while(1){usleep(1200000);//停止时间岔开,不然打印出来乱了cout << "我是主线程" << endl;       }return 0;
}

在这里插入图片描述

进程ID获取

在线程中如果想获取线程ID,可以使用pthread_self() 函数,用于获取调用线程的唯一标识符,即线程ID(Thread ID)。函数的原型如下:

在这里插入图片描述
它不接受任何参数,直接返回调用该函数的线程的ID,返回值类型为 pthread_t。通常情况下,你可以使用 pthread_self() 函数获取当前线程的ID,并将其用于线程管理、线程间通信或其他需要唯一标识线程的场景。可以将上述代码稍作修改,如下:

void *thread_run(void *args)
{for (int i = 0; i < 5; i++){cout << "run....." << i << " self id : " << pthread_self() << endl;sleep(1);}cout << "线程退出......" << endl;return nullptr;
}int main()
{pthread_t t1;pthread_create(&t1, nullptr, thread_run, nullptr);while(1){usleep(1200000);cout << "我是主线程" << endl;       }return 0;
}

在这里插入图片描述

在这里需要注意的是,这里所说的线程ID是 pthread 库给每个线程定义的进程内唯一标识,是 pthread 库来进行维护的。由于线程是进程的一个执行分支,而每个进程有自己独立的内存空间,所以这个线程ID的作用域是进程级(该进程内,内核不认识)。
而pthread 库实际上是建立在操作系统内核提供的基础之上的。当程序中使用 pthread 库创建一个新线程时,实际上是通过调用内核提供的系统调用(如 clone)来完成。这个系统调用会在内核层面创建一个新的线程。每个由内核创建的线程都会被分配一个全局唯一的标识符,用来唯一地标识这个线程。这个标识符由内核维护,对用户空间程序是不可见的,但可以通过 pthread 库提供的接口来获取。这个系统全局唯一的ID叫做线程PID,或叫做TID,也有叫做LWP。可以用命令ps -aL来查看,如下图:
在这里插入图片描述

线程终止

线程终止有异常终止与正常终止,当我们如果需要只终止某个线程而不终止整个进程,可以使用return来使进程返回终止,但是这种方法对主线程不适用,因为从main函数return相当于调用exit,会导致所有线程都退出。还有就是可以使用pthread_exit函数来终止自己,pthread_exit() 函数也是 POSIX 线程库中的一个函数,用于终止当前线程的执行并返回一个指定的退出状态。这个函数允许线程在结束时提供一个状态码,以便其他线程可以通过 pthread_join() 函数获取这个状态码。该函数原型如下:
在这里插入图片描述
retval 参数是一个指针,用于传递线程的退出状态。这个状态可以是任何指针类型的数据,通常用于传递线程的返回值或其他有用的信息。当线程调用 pthread_exit() 时,它将会立即终止,并将 retval 的值传递给等待它的线程(如果有的话)。如果不需要返回状态,可以简单地传递 NULL。

需要注意,pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,该栈上的临时对象也就销毁了。

如下代码:

void *thread_run(void *args)
{for (int i = 0; i < 5; i++){cout << "run....." << i << " self id : " << pthread_self() << endl;sleep(3);cout << "pthread_exit" << endl;pthread_exit(nullptr);}cout << "线程退出......" << endl;return nullptr;
}int main()
{pthread_t t1;pthread_create(&t1, nullptr, thread_run, nullptr);sleep(5);cout << "主线程退出" << endl;return 0;
}

新线程 t1 在运行 thread_run 函数后,进入循环打印一条信息后休眠3秒,然后打印pthread_exit再调用该函数直接退出。主线程在启动 t1 后会休眠5秒,然后打印 “主线程退出” 消息。

在这里插入图片描述
除了使用这个函数之外,一个线程还可以调用 pthread_ cancel 函数来终止同一进程中的另一个线程,原型如下:

在这里插入图片描述

pthread_cancel() 函数允许一个线程向另一个线程发送取消请求,但不会立即终止目标线程的执行。取消请求会等待目标线程在取消点处停止执行,然后才能生效。取消点是指程序中可以接收取消请求的位置,这些位置通常是系统调用或标准库函数内部的位置。例如sleep()、read()、write() 等函数都包含取消点。当目标线程执行到取消点时,取消请求将生效,目标线程会被终止。pthread_cancel 函数的返回值是0表示成功,非0值表示发生了错误。如果 pthread_cancel 返回非0值,可以使用 strerror 函数来获取错误描述。

在这里插入图片描述

如下代码:

void *thread_function(void *arg)
{for (int i = 0; i < 5; ++i){cout << "Thread is running... " << i << endl;sleep(1);}cout << "Thread execution completed." << endl;pthread_exit(NULL);
}int main()
{pthread_t tid;pthread_create(&tid, NULL, thread_function, NULL);sleep(2);pthread_cancel(tid);cout << "Main thread exiting." << endl;return 0;
}

主线程在休眠2秒后向tid线程发送取消请求,tid线程退出。

在这里插入图片描述

线程等待

与进程等待类似,有时候我们让线程去执行某个任务,我们需要知道执行的结果,因此就需要进行线程等待。除此之外,有时候线程等待还要收集线程结果,线程可能会返回一个结果或执行一些计算,而父线程需要获取这些结果或计算的值。通过线程等待父线程可以获得子线程的退出状态或其他返回值。线程等待还可以进行资源回收,当一个线程结束时,它可能会占用一些系统资源(如内存)。因此可以使用线程等待确保线程结束后,相关资源能够被正确释放,从而避免资源泄漏。进行线程等待可以使用 pthread_join 函数。这个函数的主要作用是阻塞当前线程,直到指定的线程完成执行为止。以下是 pthread_join 函数的原型:

在这里插入图片描述

thread 参数是要等待的目标线程的线程ID。retval 参数是一个指向指针的指针(即二级指针),用于存储目标线程的退出状态。如果不需要获取线程的退出状态,可以将 retval 设置为 NULL。pthread_join() 函数的返回值是一个整数,用于表示函数调用的成功或失败。通常,成功时返回0,失败时返回一个非零值,表示出现了错误。

在这里插入图片描述

如下代码:

void *thread_function(void *arg)
{for (int i = 0; i < 5; ++i){cout << "Thread is running... " << i << endl;sleep(1);}cout << "Thread execution completed." << endl;pthread_exit(NULL);
}int main()
{pthread_t tid;pthread_create(&tid, NULL, thread_function, NULL);pthread_join(tid, nullptr);cout << "Main thread exiting." << endl;return 0;
}

在这里插入图片描述

线程分离

线程分离是一种线程属性,用于定义线程的生命周期和资源管理方式。在默认情况下,新创建的线程是 joinable 的,也就是需要被 pthread_join 等待的,否则线程退出后,无法释放资源,从而造成系统泄漏。如果不关心线程的返回值,需要手动的进行 pthread_join 反而是一种负担,这个时候可以直接进行线程分离,也就是告诉系统,当线程退出时,自动释放线程资源。

需要注意的是,除了线程组内其他线程对目标线程进行分离外,也可以自己对自己进行线程分离(我抽我自己,没毛病吧)。还有就是线程一旦分离就不能再进行 pthread_join 了,一个线程不能既是 joinable 又是分离的。

进行线程分离可以使用 pthread_detach 函数,该函数是 POSIX 线程库中的一个函数,用于将线程设置为分离状态。函数原型如下:

在这里插入图片描述

thread参数为要设置为分离状态的线程的线程标识符(线程ID)。如果分离成功,函数返回0。如果失败,函数返回一个非零错误码,表示设置线程分离状态时发生了错误。如下代码:

void *thread_function(void *arg)
{pthread_detach(pthread_self());for (int i = 0; i < 5; ++i){cout << "Thread is running... " << i << endl;sleep(1);}cout << "Thread execution completed." << endl;pthread_exit(NULL);
}int main()
{pthread_t tid;pthread_create(&tid, NULL, thread_function, NULL);// pthread_detach(tid); 也可以在这里分离sleep(2);if (pthread_join(tid, nullptr) == 0)cout << "wait success" << endl;elsecout << "wait fail" << endl;cout << "Main thread exiting." << endl;return 0;
}

代码中,在主线程创建tid线程后,休眠2秒确保tis线程先进行分离,然后主线程对tid线程进行等待,由于线程在分离后是不能够进行等待的,所以主线程会打印 wait fail 然后结束线程,主线程结束tid线程也会跟着结束,结果如下。

在这里插入图片描述

总结

文章介绍了多线程的概念以及优缺点,并对线程和进程进行比较分析,最后介绍了Linux中线程控制的一些函数方法,如线程的创建,线程的终止,线程的分离等等,总的来说多线程是一种强大的编程技术,适用于需要同时处理多个任务或实现高并发性能的应用程序。然而,它需要谨慎的设计和管理,以确保线程安全性和避免常见的多线程问题。

码文不易,如果客官觉得文章对你有所帮助的话,就点一个小小的👍吧!

在这里插入图片描述

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

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

相关文章

入侵防御系统(IPS)网络安全设备介绍

入侵防御系统&#xff08;IPS&#xff09;网络安全设备介绍 1. IPS设备基础 IPS定义 IPS&#xff08;Intrusion Prevention System&#xff09;是一种网络安全设备或系统&#xff0c;用于监视、检测和阻止网络上的入侵尝试和恶意活动。它是网络安全架构中的重要组成部分&…

MyBatis中的ResultMap有什么作用

MyBatis是一款广泛使用的Java持久层框架&#xff0c;它简化了数据库访问和数据映射的工作。在MyBatis中&#xff0c;ResultMap是一个强大的工具&#xff0c;用于将数据库查询结果映射到Java对象上。本文将深入探讨MyBatis中的ResultMap&#xff0c;解释它的作用以及如何使用它来…

进程状态的理解

我们知道进程会有属于自己的PCB&#xff0c;便于操作系统的管理&#xff0c;而PCB结构体里面还有进程状态参数&#xff0c;类似于用一个变量标识对应的进程状态&#xff0c;就相当于将每个进程状态编号&#xff0c;而PCB中有一个变量存储当前进程状态所对应的编号&#xff0c;也…

解决WordPress升级后提示:无需升级,您的WordPress数据库已经是最新的了

问题描述 当升级了 WordPress 6.3 后&#xff0c;登录后台出现了提示&#xff1a;无需升级&#xff0c;您的WordPress 数据库已经是最新的了。并且无法进入后台了。 出现这个问题的原因可能是你网站开启了 Memcached 缓存。 如何验证是否开启了 Memcached 缓存&#xff1f;检…

php 安装mongodb扩展模块,rdkafka模块

mongodb mongodb扩展下载 选择php版本&#xff0c;根据报错提示&#xff0c;选择扩展对应的版本选择非安全进程将php_mongodb.dll放到php/ext目录下修改php.ini配置&#xff0c;添加extensionphp_mongodb.dll开启php_mongodb扩展&#xff0c;重启服务php -m 查看是否开启成功…

排序(order by)

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: select */列名 from 表名 order by 列名1 asc/desc, 列名2 asc/desc; 说明&#xff1a; 排序的目的&#xff1a;改变查询结果的返回顺序…

大数据软件项目的数据清洗

大数据软件项目中的数据清洗是数据预处理过程中的重要环节&#xff0c;用于识别和纠正数据集中的错误、不一致性和不完整性。虽然没有专门的"数据清洗开发框架"&#xff0c;但有许多工具和库可用于数据清洗任务。以下是一些常见的数据清洗工具和库&#xff0c;可以与…

win10 U盘安装教程

一年内&#xff0c;第三次重装电脑了&#xff0c;我必须要写一份教程了。从制作U盘开始&#xff0c;到重装系统&#xff0c;全部都记录一下&#xff0c;以备不时之需。 首先&#xff0c;找一个U盘&#xff0c;如果U盘内有需要文件&#xff0c;请自行备份&#xff0c;因为这个U盘…

JVM(Java虚拟机)

目录 1.JVM 简介 1.1 JVM 发展史 1.Sun Classic VM 2.Exact VM 3.HotSpot VM 4.JRockit 5.J9 JVM 6.Taobao JVM&#xff08;国产研发&#xff09; 1.2 JVM 和《Java虚拟机规范》 2. JVM 运行流程 JVM 执行流程 3. JVM 运行时数据区 3.1 堆&#xff08;线程共享&…

泛型的小结

文章目录 什么是泛型泛型的相关概念泛型的作用 泛型的使用泛型类语法泛型接口语法泛型方法语法泛型类的简单示例泛型接口的简单示例基于泛型的简单工厂方法泛型的上界与下界 泛型的一些使用建议 什么是泛型 从JDK1.5开始引入泛型&#xff08;generic&#xff09;语法。对类型实…

一文看懂光模块的工作原理

你们好&#xff0c;我的网工朋友 光模块有很多类别&#xff0c;是我们经常要用到的PHY层器件。虽然封装&#xff0c;速率&#xff0c;传输距离有所不同&#xff0c;但是其内部组成基本是一致的。 以太网交换机常用的光模块有SFP&#xff0c;GBIC&#xff0c;XFP&#xff0c;X…

【Linux】 rm命令使用

作为一个程序员 我们经常用到rm -rf * 或者rm -rf XXX 。但是rm -rf 是什么意思不是很清楚&#xff0c;咱们一起来学习一下吧。 rm&#xff08;英文全拼&#xff1a;remove&#xff09;命令用于删除一个文件或者目录。 rm 命令 -Linux手册页 著者 由保罗鲁宾、大卫麦肯齐、理…

10.8队列安排,最少找字典次数,表达式转换与计算模拟(栈、队列)

队列安排1160 灵活的插入与删除 用队列实现的话&#xff0c;就是双端队列&#xff0c; 第一阶段是要找到对应编号的同学&#xff0c;然后根据p的取值决定是怎么插入 第二阶段也是要找到对应编号同学&#xff0c;之后就删除&#xff0c;如果找不到就返回 思路是这个思路&…

为什么团队需要实时协作?该如何实现?

协作是任何组织成功的关键部分&#xff0c;通过明确定义的愿景和使命并基于透明度和持续沟通来执行。 实时的协作是指员工之间就不同的项目、任务、文件或文档进行同步、无缝的互动和协作&#xff0c;他们几乎不受任何地理边界的限制&#xff0c;即时沟通和分享反馈、想法和信…

【AI视野·今日Robot 机器人论文速览 第四十七期】Wed, 4 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Wed, 4 Oct 2023 Totally 40 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;基于神经网络的多模态触觉感知, classification, position, posture, and force of the grasped object多模态形象的解耦(f…

从零开始的C++(七)

1.malloc、free和new、delete的区别&#xff1a; 1、.malloc、free是函数&#xff0c;new、delete是运算符。 2、malloc不会调用构造函数&#xff0c;new可以调用构造函数。 3、malloc开辟失败返回NULL&#xff0c;new失败会捕捉异常。 4、malloc不会自动计算类型大小&…

好奇喵 | PT(Private Tracker)——什么是P2P,什么是BT,啥子是PT?

前言 有时候会听到别人谈论pt&#xff0c;好奇猫病又犯了&#xff0c;啥子是pt&#xff1f; PT——你有pt吗&#xff1f;啥是pt&#xff1f; 从BT开始 BitTorrent是一种点对点&#xff08;P2P&#xff09;文件共享协议&#xff0c;用于高速下载和上传大型文件。它允许用户通…

钡铼BL124PN:简单快速转换Profinet到Ethernet/IP

钡铼技术BL124PN是一款高性能的Profinet转Ethernet/IP网关设备。该网关专为工业自动化领域设计&#xff0c;用于实现不同协议之间的互连和通信。BL124PN采用可靠稳定的硬件和先进的通信技术&#xff0c;具有以下主要特点&#xff1a; 协议转换能力&#xff1a;BL124PN能够将Pr…

暴力破解及验证码安全

1.暴力破解注意事项 1、破解前一定要有一个有郊的字典&#xff08;Top100 TOP2000 csdn QQ 163等密码&#xff09; https://www.bugku.com/mima/ 密码生成器 2、判断用户是否设置了复杂的密码 在注册页面注册一个,用简单密码看是否可以注册成功 3、网站是…

RabbitMQ-网页使用消息队列

1.使用消息队列 几种模式 从最简单的开始 添加完新的虚拟机可以看到&#xff0c;当前admin用户的主机访问权限中新增的刚添加的环境 1.1查看交换机 交换机列表中自动新增了刚创建好的虚拟主机相关的预设交换机。一共7个。前面两个 direct类型的交换机&#xff0c;一个是…