Python的并发并行[0] -> 基本概念
基本概念 / Basic Concept
快速跳转
- 进程 / Process
- 线程 / Thread
- 协程 / Coroutine
- 全局解释器锁 / Global Interpreter Lock
- 守护线程 / Daemon Thread
- 信号量 / Semaphore
- 有界信号量 / BoundedSemaphore
- 同步原语 / Synchronization Primitive
- 锁 / Lock
- 互斥锁和可重入锁 / Mutex Lock and Reentrant Lock
- 死锁 / Deadlock
- 线程安全 / Thread Safety
0 简介与动机 / Why Multi-Thread/Multi-Process/Coroutine
在多线程(multithreaded, MT)编程出现之前,计算机程序的执行是由单个步骤序列组成的,该序列在主机的CPU中按照同步顺序执行。即无论任务多少,是否包含子任务,都要按照顺序方式进行。
然而,假定子任务之间相互独立,没有因果关系,若能使这些独立的任务同时运行,则这种并行处理方式可以显著提高整个任务的性能,这便是多线程编程。
而对于Python而言,虽然受限于GIL(全局解释器锁)的控制,在处理计算密集型程序时,多线程可能并不能提升性能,但对于I/O密集型的程序来说,Python的多线程模式就能很好的起到性能提升的作用。
1 相关名词 / Relevant Noun
1.0 进程 / Process
是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体,是一个执行中的程序,也被称为重量级进程。
1.1 线程 / Thread
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
1.2 协程 / Coroutine
协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。它本身是一种特殊的子程序或者称作函数。
一个程序可以包含多个协程,可以对比与一个进程包含多个线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
1.3 全局解释器锁 / Global Interpreter Lock
全局解释器锁GIL是计算机程序设计语言解释器用于同步线程的工具,使得解释器任何时刻仅有一个线程在执行。常见例子有CPython(JPython不使用GIL)与Ruby MRI。
正式由于全局解释器锁的存在,使得Python解释器在任意时刻只能以单线程的形式运行,即Python中的多线程实际上是对多线程中的每个线程执行一定内存数量的程序后,切换到另一个线程继续执行,直到再次切回继续执行。因此Python应对CPU-bound Computation时难以体现优势,而处理类似爬虫等I/O-intensive Computation时则有较大优势。
1.4 守护线程 / Daemon Thread
所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。即守护线程的存在不影响程序退出。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开,如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
1.5 信号量 / Semaphore
Semaphore是最古老的同步原语之一,由荷兰计算机科学家 Edsger W. Dijkstra 发明。(他最早使用名为P()和V()的函数对应acquire()和release())。threading模块中,Semaphore在内部管理着一个计数器。调用acquire()会使这个计数器-1,release()则是+1。计数器的值永远不会小于 0,当计数器到0时,再调用acquire()就会阻塞,直到其他线程来调用release()。Semaphore 也支持上下文管理协议。
1.6 有界信号量 / BoundedSemaphore
threading模块中的一个工厂函数,返回一个新的有界信号量对象。一个有界信号量会确保它当前的值不超过它的初始值。如果超过,则引发ValueError。在大部分情况下,信号量用于守护有限容量的资源。如果信号量被释放太多次,它是一种有bug的迹象。如果没有给出,value默认为1。
1.7 同步原语 / Synchronization Primitive
当一个进程调用一个send原语时,在消息开始发送后,发送进程便处于阻塞状态,直至消息完全发送完毕,send原语的后继语句才能继续执行。当一个进程调用一个receive原语时,并不立即返回控制,而是等到把消息实际接收下来,并把它放入指定的接收区,才返回控制,继续执行该原语的后继指令。在这段时间它一直处于阻塞状态。上述的send和receive被称为同步通信原语或阻塞通信原语。事件作为一种同步原语,是计算机科学中的一种同步机制,用来指示等待中的进程特定条件已经变为真。
1.8 锁 / Lock
对于锁来说,其实锁的本质是一个线程之间约定的控制权,即锁的作用实质上并不能将某一资源进行锁定,使其他线程无法修改。锁的本质在于,多个线程之间对某一个锁进行约定,约定这把锁对应的公共资源,当需要对这把锁对应的资源进行修改时,必须拥有这把锁的权限才能进行。因此,当需要修改某资源时,就需要尝试获取对应的锁,而当这把锁的权限被其他线程获取时,其余需要获取的线程就会进入阻塞等待状态。且当锁释放时,权限的获取是随机的,不论进入阻塞的时间先后。
1.9 互斥锁和可重入锁 / Mutex Lock and Reentrant Lock
对于互斥锁和可重用锁,互斥锁只能被获取一次,若多次获取则会产生阻塞,需等待原锁释放后才能再次入锁。而可重入锁则可被本线程多次acquire入锁,但是要求入锁次数与释放次数相同,才能完全解锁,且锁的释放需要在同一个线程中进行
Note: 对于可重入锁来说,可多次入锁的特性仅在本线程有效,也就是说,即使是可重入锁,被一个线程获取锁定时,其他线程无法再次进入,只有本线程可以。
1.10 死锁 / Deadlock
死锁出现在一个资源被多次调用,而调用方均未能释放资源,便会造成死锁现象。死锁大致可分为两种形式出现,迭代死锁和相互调用死锁。一般死锁是有互斥锁造成的,使用可重入锁则可以避免部分死锁问题。
1.11 线程安全 / Thread Safety
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
参考链接
《Python 核心编程 第三版》
Python的并发并行[0] -> 基本概念的更多相关文章
- Python的并发并行[3] -> 进程[0] -> subprocess 模块
subprocess 模块 0 模块描述 / Module Description From subprocess module: """Subprocesses wit ...
- Python的并发并行[1] -> 线程[0] -> threading 模块
threading模块 / threading Module 1 常量 / Constants Pass 2 函数 / Function 2.1 setprofile()函数 函数调用: thread ...
- Python的并发并行[2] -> 队列[0] -> queue 模块
queue 模块 / queue Module 1 常量 / Constants Pass 2 函数 / Function Pass 3 类 / Class 3.1 Queue类 类实例化:queue ...
- Python的并发并行[4] -> 并发[0] -> 利用线程池启动线程
利用线程池启动线程 submit与map启动线程 利用两种方式分别启动线程,同时利用with上下文管理来对线程池进行控制 from concurrent.futures import ThreadPo ...
- Python的并发并行[1] -> 线程[1] -> 多线程的建立与使用
多线程的建立与使用 目录 生成线程的三种方法 单线程与多线程对比 守护线程的设置 1 生成线程的三种方法 三种方式分别为: 创建一个Thread实例,传给它一个函数 创建一个Thread实例,传给它一 ...
- Python的并发并行[1] -> 线程[2] -> 锁与信号量
锁与信号量 目录 添加线程锁 锁的本质 互斥锁与可重入锁 死锁的产生 锁的上下文管理 信号量与有界信号量 1 添加线程锁 由于多线程对资源的抢占顺序不同,可能会产生冲突,通过添加线程锁来对共有资源进行 ...
- Python的并发并行[1] -> 线程[3] -> 多线程的同步控制
多线程的控制方式 目录 唤醒单个线程等待 唤醒多个线程等待 条件函数等待 事件触发标志 函数延迟启动 设置线程障碍 1 唤醒单个线程等待 Condition类相当于一把高级的锁,可以进行一些复杂的线程 ...
- Python的并发并行[2] -> 队列[1] -> 使用队列进行任务控制
使用队列进行任务控制 1 FIFO与LIFO队列 FIFO(First In First Out)与LIFO(Last In First Out)分别是两种队列形式,在FIFO中,满足先入先出的队列方 ...
- Python的并发并行[3] -> 进程[1] -> 多进程的基本使用
多进程的基本使用 1 subprocess 常用函数示例 首先定义一个子进程调用的程序,用于打印一个输出语句,并获取命令行参数 import sys print('Called_Function.py ...
随机推荐
- 修改MySQL数据库字符集
Preface I've demonstrated how to change character set in Oracle database in my previous blog ...
- Java中Set的contains()方法——hashCode与equals方法的约定及重写原则
转自:http://blog.csdn.net/renfufei/article/details/14163329 翻译人员: 铁锚 翻译时间: 2013年11月5日 原文链接: Java hashC ...
- cloud-init介绍及源码解读
https://zhuanlan.zhihu.com/p/27664869 知乎大神写的
- initialization of 'zf' is skipped by 'case' label原因及解决方法
原因:switch 的 case 中不能定义变量,不然就会报错.可能是变量的初始化会因为有时候case条件不被执行而跳过. 解决方法: 1:在case中用{}将代码括起来,这样在{}中就能定义变量了; ...
- SqlServer中截取小数位数
方法一:convert(float,字段名) as 别名 select convert(float,round(10.123232,2)) 结果:10.12 select convert(float, ...
- Android 热更新是如何实现的?
Android开发中,我们常常遇到热更新这个概念,而这个热更新具体是怎么实现的呢?今天在网上看到一个大神分享的热更新相关实现原理和实现代码,感觉灰常不错,分享给广大码农盆友look look . Cl ...
- [NC2018-9-9T1]中位数
题目大意:给你一个长度为$n$的序列,要求出长度大于等于$len$的字段的中位数中最大的一个中位数 题解:可以二分答案,对于比它小的数赋成$-1$,大的赋成$1$.求前缀和,若有一段区间的和大于$0$ ...
- HDU - 5919 Sequence II
题意: 给定长度为n的序列和q次询问.每次询问给出一个区间(L,R),求出区间内每个数第一次出现位置的中位数,强制在线. 题解: 用主席树从右向左的插入点.对于当前点i,如果a[i]出现过,则把原位置 ...
- 【CF Round 429 B. Godsend】
time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...
- HDU 1054树形DP入门
Strategic Game Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...