我记不住的线程pthreads及NPTL

2024-02-22 04:52
文章标签 线程 pthreads 记不住 nptl

本文主要是介绍我记不住的线程pthreads及NPTL,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景:平时所能接触的语言是C、Java和Python,写代码的时候需要对多线程的使用和原理有一定的理解才能写出优质的代码,所以这里说一下POSIX thread和pthreads以及C/Java/Python所用的线程模型以及具体的实现及测试。

1. 线程产生和历史

单进程-->>多进程-->>多线程

进程是对程序运行过程中所涉及的数据、代码、资源和执行信息的封装,起到管理的作用。

早期计算机只支持单进程,其实就是独占CPU和内存等资源进行批处理作业,执行完一个再次执行下一个,此时CPU的处理速度是一个瓶颈,都是计算密集型的作业。

后面演化出了多进程,可以在写文档的同时听歌曲,使用并发(concurrency)的方式执行,从人的角度来看感觉多个程序是并行(parallelism)的,其实对于计算机来说是通过时间片和切换进程来实现交替运行,这会导致上下文切换。

多线程的引入是为了进一步在设计、性能、易用性上优化多进程,最早也称作子进程(sub-process)或轻量级进程(light-weight process),是把一个进程拆分成多个线程,进程负责线程共享资源的管理,线程负责执行,以及后续的上下文切换也变为了线程的切换,原来进程负责的栈、程序计数器、寄存器现在有线程负责,如下图所示。而多线程可以使一个程序运行在多个CPU核上,使代码的执行效率更高。如果只是支持多进程,只会让多进程在多个CPU核上并行执行,而各个进程的内部的各类逻辑不能并行执行。在引入了多线程后,不仅多个程序可以并行执行,各个程序的内部也可以并行执行,也就是说 颗粒度更小了。

当处理多个计算密集型的作业的时候会有很多线程运行,会导致各个线程运行的时间片较短,会进行频繁的切换上下文,线程的切换包括了上下文的切换耗时和线程调度的耗时,同时CPU缓存也会失效,所以频繁的切换上下文会导致程序执行变慢,即 不是线程越多,程序执行的就会越快。

2. 线程模型

线程模型:用户线程如何映射到内核线程,用户线程在用户空间,内核线程在内核空间中,

1:1 (kernel-level threading) 一个用户线程对应一个内核线程,优点是简单是真正的线程,缺点是当进行系统调用的时候会进行用户态和内核态之间的上下文切换,相对耗时。线程的调度由操作系统完成。

M:1 (user-level threading) 多个用户线程对应一个内核线程,多个用户线程的调度是由虚拟机完成优点是应用程序操作用户线程无需进行系统调用的上下文切换,缺点是无法完全利用多核处理器,同时当一个用户线程堵塞会被操作系统的系统调度让出时间片,也就是说这个线程不再Ready了不再分配时间片了,导致其他用户线程也无法工作了。所以为了避免这种情况,可以使用非堵塞的方法或函数进行。这些用户线程也可以称作协程。

M:N (hybrid threading) 多个用户线程对应多个内核线程,较为复杂,go语言的线程就使用这个模型来实现也称作为协程。

3. 任务类型(cpu_bound/io_bound)

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低.计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用

IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差

4. 操作系统的提供的线程以及C语言的使用

POSIX(Portable Operating System Interface)的出现是为了统一各类操作系统的接口,使程序能更好的移植。

POSIX threads 也称作为Pthreads,一个单进程可以包含多个线程,多个线程共享同样的全局内存包括全局变量和堆段,但是每个线程有自己的栈存放一些局部变量。

POSIX thread(pthread)不是一个具体的关于线程的实现,而是一个API规范和标准,它在<pthread.h>中定义了各类以pthread_开头的函数,用于线程的创建、取消、退出、join等。

而LinuxThreads和NPTL(Native POSIX Thread Library)是依据POSIX Thread规范进行具体实现的两种不同的方式。自glibc 2.3.2版本开始NPTL就能使用了,NPTL是一种更现代的实现方式以及更好的性能

The current version of POSIX.1 is IEEE Std 1003.1-2017.

gcc xxx.c -lpthread 

gcc xxx.c -pthread

我们直接man 7 pthreads来查看一下关于pthreads的介绍和描述。

如何查看当前Linux系统中我们的线程具体的实现是哪一种呢?

 而自glibc 2.3.2版本开始NPTL就能使用了,我们先看一下glibc使用的是哪个版本。

查看glibc的版本号?

ldd --version

或找到一个命令,这里假设ls,我们使用ldd命令分析ls命令的依赖:

然后我们执行一下libc.so.6,注意这个库是可以直接执行的

或者直接执行 $(ldd /bin/ls | grep libc.so |awk '{print $3}') 来进行版本的查看

查看线程的实现方式?

$ getconf GNU_LIBPTHREAD_VERSION

5. 操作系统的线程和Java的线程的区别和联系

Java对线程的实现有两种方法,第一个是GreenThread,第二个是NativeThread。Java早期实现是GreenThread已经被废弃,Java更现代的实现是NativeThread。

GreenThread采用了线程模型M:1(user-level threading),开发这个线程库的团队叫做GreenTeam,所以这个线程库也称作GreenThread,这个线程库存在于早期的JDK1.1/1.2里面。

Java后期提供的NativeThread线程库采用的是1:1 (kernel-level threading),只不过是对操作系统提供的操作线程的系统调用的二次封装,线程的调度完全有操作系统完成,因此Java线程库非常简单。但是因为Java是跨平台的,所以需要屏蔽各种操作系统之间的差异,例如Windows和Linux,需要做一个中间层并重新定义线程的状态和优先级。

Linux操作系统的线程状态:New、Ready、Running、Waiting、Terminated

Java的线程状态:New、Runnable、Waiting、Timed_Waiting、Blocked、Terminated

也就是说Java重新定义了线程的状态、线程优先级、触发条件以及与操作系统的线程状态的映射关系

6. 操作系统的线程和Python的线程的区别和联系

Python的线程库也是采用了1:1 (kernel-level threading)也是对操作系统的线程的系统调用进行的二次封装,线程的调度由操作系统完成,区别在于Python存在GIL锁(Global Interpreter Lock),多线程在Python中只能交替运行,无法利用多核的CPU,即有多个线程运行,只能在一个核心上交替运行,无法使用多核并行处理,多线程处理计算密集型CPU_bound任务,如下图所示:

import threading, multiprocessingprint(multiprocessing.cpu_count())
def loop():x = 0while True:x = x ^ 1for i in range(multiprocessing.cpu_count()):t = threading.Thread(target=loop)t.start()

上图为以进程进行查看,整体是28.2us,下图按 H 以线程进行查看,共4个线程在运行,基本都是在25%左右,也就是说这4个线程只跑在了这一个核心上,无法多核心并行处理。

我使用的是四核心处理器,但是我使用Python多线程死循环执行计算密集型任务CPU-bound,跑不满这四个核心。

而使用C语言或Java语言进行执行可以跑满四个核心,我使用C语言及Java跑了一下,如下图所示:

使用C语言运行计算密集型任务(CPU_bound),CPU能跑满98.3us,下面的进程能跑到384.9,说明咱们四个核心已经跑满了,同时我们以 线程方式查看一下,按H即可显示如下图所示:

四个线程也可以跑满,几乎在95%左右。

同理我们使用Java语言进行测试:

CPU能跑满99.1us, 下面的进程能跑满到383.6,说明咱们使用Java语言跑计算密集型任务(CPU_bound)四个核心已经跑满了。

GIL的作用使开发者在写代码时无需担心线程安全的问题,同时限制了多线程在执行计算密集型CPU-bound的性能。而对于io-bound(http请求响应或文件读写)来说,GIL的影响不大仍然可以使用多线程提升性能。 GIL只影响的是计算密集型的性能,对io密集型来说无多大影响。

对于同样的io密集型任务:

对于io操作来看,进程和线程的区别不大,但是进程的开销和代价更大,为了节省资源使用线程来处理io操作更优。

对于同样的cpu密集型任务:

对于cpu密集型来看,python的进程比线程更优,在python线程处理cpu密集型任务时候GIL无法利用多核来提升执行效率,此时使用python的进程更优。

总之:python语言 Multithreading for IO-bound tasks. Multiprocessing for CPU-bound tasks.

Python也在移除GIL,短期计划是在版本3.13或3.14进行移除。

参考资料:

1. https://en.wikipedia.org/wiki/Green_thread

2. https://en.wikipedia.org/wiki/Pthreads

3. https://en.wikipedia.org/wiki/Thread_(computing)

4. Vonage API Developer

5. Difference Between Multithreading vs Multiprocessing in Python - GeeksforGeeks

6. Operating Systems: Threads

7. https://www.cs.rochester.edu/u/ferguson/csc/c/c-for-java-programmers.pdf

8. 进程 vs. 线程 - 廖雪峰的官方网站

这篇关于我记不住的线程pthreads及NPTL的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/734189

相关文章

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api

java线程深度解析(六)——线程池技术

http://blog.csdn.net/Daybreak1209/article/details/51382604 一种最为简单的线程创建和回收的方法: [html]  view plain copy new Thread(new Runnable(){                @Override               public voi

java线程深度解析(五)——并发模型(生产者-消费者)

http://blog.csdn.net/Daybreak1209/article/details/51378055 三、生产者-消费者模式     在经典的多线程模式中,生产者-消费者为多线程间协作提供了良好的解决方案。基本原理是两类线程,即若干个生产者和若干个消费者,生产者负责提交用户请求任务(到内存缓冲区),消费者线程负责处理任务(从内存缓冲区中取任务进行处理),两类线程之

java线程深度解析(四)——并发模型(Master-Worker)

http://blog.csdn.net/daybreak1209/article/details/51372929 二、Master-worker ——分而治之      Master-worker常用的并行模式之一,核心思想是由两个进程协作工作,master负责接收和分配任务,worker负责处理任务,并把处理结果返回给Master进程,由Master进行汇总,返回给客

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的

java线程深度解析(一)——java new 接口?匿名内部类给你答案

http://blog.csdn.net/daybreak1209/article/details/51305477 一、内部类 1、内部类初识 一般,一个类里主要包含类的方法和属性,但在Java中还提出在类中继续定义类(内部类)的概念。 内部类的定义:类的内部定义类 先来看一个实例 [html]  view plain copy pu

C#线程系列(1):BeginInvoke和EndInvoke方法

一、线程概述 在操作系统中一个进程至少要包含一个线程,然后,在某些时候需要在同一个进程中同时执行多项任务,或是为了提供程序的性能,将要执行的任务分解成多个子任务执行。这就需要在同一个进程中开启多个线程。我们使用 C# 编写一个应用程序(控制台或桌面程序都可以),然后运行这个程序,并打开 windows 任务管理器,这时我们就会看到这个应用程序中所含有的线程数,如下图所示。

71-java 导致线程上下文切换的原因

Java中导致线程上下文切换的原因通常包括: 线程时间片用完:当前线程的时间片用完,操作系统将其暂停,并切换到另一个线程。 线程被优先级更高的线程抢占:操作系统根据线程优先级决定运行哪个线程。 线程进入等待状态:如线程执行了sleep(),wait(),join()等操作,使线程进入等待状态或阻塞状态,释放CPU。 线程占用CPU时间过长:如果线程执行了大量的I/O操作,而不是CPU计算

使用条件变量实现线程同步:C++实战指南

使用条件变量实现线程同步:C++实战指南 在多线程编程中,线程同步是确保程序正确性和稳定性的关键。条件变量(condition variable)是一种强大的同步原语,用于在线程之间进行协调,避免数据竞争和死锁。本文将详细介绍如何在C++中使用条件变量实现线程同步,并提供完整的代码示例和详细的解释。 什么是条件变量? 条件变量是一种同步机制,允许线程在某个条件满足之前进入等待状态,并在条件满

pythons强行杀掉线程的方法

使用ctypes强行杀掉线程 import threading import time import inspect import ctypes def _async_raise(tid, exctype): """raises the exception, performs cleanup if needed""" tid = ctypes.c_long(tid) if n