做者简介:html
姓名:黄志成(小黄)博客: 博客python
操做系统原理相关的书,基本都会提到一句很经典的话: "进程是资源分配的最小单位,线程则是CPU调度的最小单位"。web
线程是操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中能够并发多个线程,每条线程并行执行不一样的任务
好处 :安全
1.易于调度。 2.提升并发性。经过线程可方便有效地实现并发性。进程可建立多个线程来执行同一程序的不一样部分。 3.开销少。建立线程比建立进程要快,所需开销不多。 4.利于充分发挥多处理器的功能。经过建立多线程进程,每一个线程在一个处理器上运行,从而实现应用程序的并发性,使每一个处理器都获得充分运行。
在解释python多线程的时候. 先和你们分享一下 python 的GIL 机制。多线程
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。并发
在多线程环境中,Python 虚拟机按如下方式执行:app
首先须要明确的一点是GIL并非Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。Python一样一段代码能够经过CPython,PyPy,Psyco等不一样的Python执行环境来执行。像其中的JPython就没有GIL。然而由于CPython是大部分环境下默认的Python执行环境。因此在不少人的概念里CPython就是Python,也就想固然的把GIL归结为Python语言的缺陷。因此这里要先明确一点:GIL并非Python的特性,Python彻底能够不依赖于GIL函数
还有,就是在作I/O操做时,GIL老是会被释放。对全部面向I/O 的(会调用内建的操做系统C 代码的)程序来讲,GIL 会在这个I/O 调用以前被释放,以容许其它的线程在这个线程等待I/O 的时候运行。若是是纯计算的程序,没有 I/O 操做,解释器会每隔 100 次操做就释放这把锁,让别的线程有机会执行(这个次数能够经过 sys.setcheckinterval 来调整)若是某线程并未使用不少I/O 操做,它会在本身的时间片内一直占用处理器(和GIL)。也就是说,I/O 密集型的Python 程序比计算密集型的程序更能充分利用多线程环境的好处。高并发
各个状态说明:ui
可能有3种状况从Running进入Blocked:
threading.Lock()不容许同一线程屡次acquire(), 而RLock容许, 即屡次出现acquire和release
上面介绍了这么多理论.下面咱们用python提供的threading模块来实现一个多线程的程序
threading 提供了两种调用方式:
import threading def func(n): # 定义每一个线程要运行的函数 while n > 0: print("当前线程数:", threading.activeCount()) n -= 1 for x in range(5): t = threading.Thread(target=func, args=(2,)) # 生成一个线程实例,生成实例后 并不会启动,须要使用start命令 t.start() #启动线程
class MyThread(threading.Thread): # 继承threading的Thread类 def __init__(self, num): threading.Thread.__init__(self) # 必须执行父类的构造方法 self.num = num # 传入参数 num def run(self): # 定义每一个线程要运行的函数 while self.num > 0: print("当前线程数:", threading.activeCount()) self.num -= 1 for x in range(5): t = MyThread(2) # 生成实例,传入参数 t.start() #启动线程
两种方式均可以调用咱们的多线程方法。
运行下面的代码,看看结果.
import threading def func(n): while n > 0: print("当前线程数:", threading.activeCount()) n -= 1 for x in range(5): t = threading.Thread(target=func, args=(2,)) t.start() print("主线程:", threading.current_thread().name)
运行结果:
当前线程数: 2 当前线程数: 2 当前线程数: 2 当前线程数: 2 当前线程数: 2 当前线程数: 3 当前线程数: 3 当前线程数: 3 主线程: MainThread 当前线程数: 3 当前线程数: 3
那咱们如何阻塞子线程让他们运行完,在继续后面的操做呢.这个时候join()方法就派上用途了. 咱们改写代码:
import threading def func(n): while n > 0: print("当前线程数:", threading.activeCount()) n -= 1 threads = [] #运行的线程列表 for x in range(5): t = threading.Thread(target=func, args=(2,)) threads.append(t) # 将子线程追加到列表 t.start() for t in threads: t.join() print("主线程:", threading.current_thread().name)
join的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,若是结束则跳转执行下一个线程的join函数。
先看看这个:
一个进程能够开启多个线程,那么多么多个进程操做相同数据,势必会出现冲突.那如何避免这种问题呢?
import threading,time num = 10 #共享变量 def func(): global num lock.acquire() # 加锁 num = num - 1 lock.release() # 解锁 print(num) threads = [] lock = threading.Lock() #生成全局锁 for x in range(10): t = threading.Thread(target=func) threads.append(t) t.start() for t in threads: t.join()
经过 threading.Lock() 咱们能够申请一个锁。而后 acquire 方法进入临界区.操做完共享数据 使用 release 方法退出.
临界区的概念: 百度百科
在这里补充一下:Python的Queue模块是线程安全的.能够不对它加锁操做.
聪明的同窗 会发现一个问题? 我们不是有 GIL 吗 为何还要加锁?
这个问题问的好!咱们下一节,将对这个问题进行探讨.
GIL的锁是对于一个解释器,只能有一个thread在执行bytecode。因此每时每刻只有一条bytecode在被执行一个thread。GIL保证了bytecode 这层面上是线程是安全的.
可是若是你有个操做一个共享 x += 1,这个操做须要多个bytecodes操做,在执行这个操做的多条bytecodes期间的时候可能中途就换thread了,这样就出现了线程不安全的状况了。
总结:同一时刻CPU上只有单个执行流不表明线程安全。
互斥锁 同时只容许一个线程更改数据,而Semaphore是同时容许必定数量的线程更改数据 ,好比厕全部3个坑,那最多只容许3我的上厕所,后面的人只能等里面有人出来了才能再进去。
import threading,time num = 10 def func(): global num lock.acquire() time.sleep(2) num = num - 1 lock.release() print(num) threads = [] lock = threading.BoundedSemaphore(5) #最多容许5个线程同时运行 for x in range(10): t = threading.Thread(target=func) threads.append(t) t.start() for t in threads: t.join() print("主线程:", threading.current_thread().name)
运行一下上面的代码.你会很明显的发现 每次只执行五个线程。
浅谈多进程多线程的选择: 文章连接python-多线程(原理篇): 文章连接
Python有GIL为何还须要线程同步?: 文章连接