Python线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/usr/bin/env python import threading import time def show(arg): time.sleep( 1 ) print ( 'thread' + str (arg)) for i in range ( 10 ): t = threading.Thread(target = show, args = (i,)) t.start() print ( 'main thread stop' ) |
上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。
更多方法:
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
run 线程被cpu调度后执行Thread类对象的run方法
Python GIL(Global Interpreter Lock)
首先需要明确的一点是GIL
并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print ( '--get num:' ,num ) time.sleep( 1 ) num - = 1 #对此公共变量进行-1操作 num = 100 #设定一个共享变量 thread_list = [] for i in range ( 100 ): t = threading.Thread(target = addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print ( 'final num:' , num ) |
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
加锁版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print ( '--get num:' ,num ) time.sleep( 1 ) lock.acquire() #修改数据前加锁 num - = 1 #对此公共变量进行-1操作 lock.release() #修改后释放 num = 100 #设定一个共享变量 thread_list = [] lock = threading.Lock() #生成全局锁 for i in range ( 100 ): t = threading.Thread(target = addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print ( 'final num:' , num ) |
RLock(递归锁)
说白了就是在一个大锁中还要再包含子锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #!/usr/bin/env python # -*- coding:utf-8 -*- import threading,time def run1(): print ( "grab the first part data" ) lock.acquire() global num num + = 1 lock.release() return num def run2(): print ( "grab the second part data" ) lock.acquire() global num2 num2 + = 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print ( '--------between run1 and run2-----' ) res2 = run2() lock.release() print (res,res2) if __name__ = = '__main__' : num,num2 = 0 , 0 lock = threading.RLock() for i in range ( 10 ): t = threading.Thread(target = run3) t.start() while threading.active_count() ! = 1 : print (threading.active_count()) else : print ( '----all threads done---' ) print (num,num2) |
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/usr/bin/env python # -*- coding:utf-8 -*- import threading,time def run(n): semaphore.acquire() time.sleep( 1 ) print ( "run the thread: %s
" % n) semaphore.release() if __name__ = = '__main__' : num = 0 semaphore = threading.BoundedSemaphore( 3 ) #最多允许5个线程同时运行 for i in range ( 20 ): t = threading.Thread(target = run,args = (i,)) t.start() while threading.active_count() ! = 1 : pass #print threading.active_count() else : print ( '----all threads done---' ) print (num) |
event
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #!/usr/bin/env python # -*- coding:utf-8 -*- import threading,time import random def light(): if not event.isSet(): event. set () #wait就不阻塞 #绿灯状态 count = 0 while True : if count < 10 : print ( ' 33[42;1m--green light on--- 33[0m' ) elif count < 13 : print ( ' 33[43;1m--yellow light on--- 33[0m' ) elif count < 20 : if event.isSet(): event.clear() print ( ' 33[41;1m--red light on--- 33[0m' ) else : count = 0 event. set () #打开绿灯 time.sleep( 1 ) count + = 1 def car(n): #no bug version while 1 : time.sleep( 1 ) if event.isSet(): #绿灯 print ( "car [%s] is running.." % n) else : print ( "car [%s] is waiting for the red light.." % n) event.wait() def car2(n): while 1 : time.sleep(random.randrange( 10 )) if event.isSet(): #绿灯 print ( "car [%s] is running.." % n) else : print ( "car [%s] is waiting for the red light.." % n) if __name__ = = '__main__' : event = threading.Event() Light = threading.Thread(target = light) Light.start() for i in range ( 3 ): t = threading.Thread(target = car,args = (i,)) t.start() |