python多线程之threading模块
threading模块中的对象
- 其中除了Thread对象以外,还有许多跟同步相关的对象
- threading模块支持守护线程的机制
Thread对象
直接调用法
import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
继承类方式
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
注意:当要启动这个线程时,只会去调用这个类的run方法,因此,在定义子类时,一定要去重写父类的run方法,不然在线程启动后,该线程不会做任何事情。
join和daemon问题
- join:为了让主线程等待子线程结束。
- daemon:将线程设置为守护线程,该线程就会变成设置其为守护线程的线程的“仆人”,当其“主人”要退出时,其不管自己的任务是否完成,都会跟着“主人一起退出”。
Some threads do background tasks, like sending keepalive packets, or performing periodic garbage collection, or whatever. These are only useful when the main program is running, and it's okay to kill them off once the other, non-daemon, threads have exited.
Without daemon threads, you'd have to keep track of them, and tell them to exit, before your program can completely quit. By setting them as daemon threads, you can let them run and forget about them, and when your program quits, any daemon threads are killed automatically.
#_*_coding:utf-8_*_ __author__ = 'Alex Li' import time import threading def run(n): print('[%s]------running----\n' % n) time.sleep(2) print('--done--') def main(): for i in range(5): t = threading.Thread(target=run,args=[i,]) t.start() t.join(1) print('starting thread', t.getName()) m = threading.Thread(target=main,args=[]) m.setDaemon(True) #将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 m.start() m.join(timeout=2) print("---main thread done----")
Lock对象(线程锁--互斥锁Mutex)
线程锁与GIL的区别
很多人都不明白python已经有了一个GIL了,怎么还搞一个线程锁。先给个图:
首先要明确,线程锁是用户级别的锁机制,跟解释器的GIL没有任何关系,也不会互相有任何。接下来,大家要注意GIL的描述,它说的是“同一时刻只会有一个线程在cpu上执行”,这并不代表着这个占用cpu的线程之行完后再去执行另一个线程(如果是这样的话,那就不存在多线程的说法了),那么线程很可能会在执行任务中,被打断,让出cpu(python的线程机制是调用的c的线程接口,即原生线程的调用,因此,调度方案是由操作系统决定的)。
即然是这样的,回到用python创建的线程中。上图中,红色的线程的任务是做“加1”操作,由于这个“加一”的操作并不是“原子操作”,所以,在执行时,很可能被打断。如果这时打断红色的线程是蓝色的线程,它同样是利用count(共享资源),那么由于红色的进程没有完成其任务,所以,蓝色的进程读到的count的跟红色的进程读到的值一样(即count=0)。这时,蓝色的线程正常执行,完成“加1”任务(count=1),之后,红色的进程再重新恢复执行,但当下,其拿到的“count=0”,然后完成“加1”任务,即这时“count=1”。
回过头了,我们起初想要的结果是“count=2”,而不是“count=1”。在这个过程中,我们是遵守GIL规则的,但是还是很可能会出现上述现象,这就是我说的,线程锁是用户级别的锁,跟GIL没有关系,我要考虑的业务逻辑是否需要做“原子操作”的问题,如果需要,就需要线程锁机制。
基本使用方法
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(递归锁)
说白了就是要在大锁中再次添加锁的机制
基本使用方法
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个人上厕所,后面的人只能等里面有人出来了才能再进去。
import threading,time def run(n): semaphore.acquire() time.sleep(1) print("run the thread: %s\n" %n) semaphore.release() if __name__ == '__main__': num= 0 semaphore = threading.BoundedSemaphore(5) #最多允许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)
timer对象
This class represents an action that should be run only after a certain amount of time has passed
Timers are started, as with threads, by calling their start()
method. The timer can be stopped (before its action has begun) by calling thecancel()
method. The interval the timer will wait before executing its action may not be exactly the same as the interval specified by the user.
def hello(): print("hello, world") t = Timer(30.0, hello) t.start() # after 30 seconds, "hello, world" will be printed
Events对象
An event is a simple synchronization object;
the event represents an internal flag, and threads
can wait for the flag to be set, or set or clear the flag themselves.
event = threading.Event()
# a client thread can wait for the flag to be set
event.wait()
# a server thread can set or reset it
event.set()
event.clear()
If the flag is set, the wait method doesn’t do anything.
If the flag is cleared, wait will block until it becomes set again.
Any number of threads may wait for the same event.
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
import threading,time import random def light(): if not event.isSet(): event.set() #wait就不阻塞 #绿灯状态 count = 0 while True: if count < 10: print('\033[42;1m--green light on---\033[0m') elif count <13: print('\033[43;1m--yellow light on---\033[0m') elif count <20: if event.isSet(): event.clear() print('\033[41;1m--red light on---\033[0m') else: count = 0 event.set() #打开绿灯 time.sleep(1) count +=1 def car(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()