Python学习笔记16(多线程)
Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。
一、使用多线程
线程有两种调用方式、直接调用和继承式调用。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time def hello(name): print("my name is ",name) time.sleep(3) if __name__ == "__main__": t1 = threading.Thread(target=hello,args=("zhangyan",)) #生成一个线程 t2 = threading.Thread(target=hello,args=("spring",)) #生成另一个线程 t1.start() #启动t1 t2.start() #启动t2
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time class MyThread(threading.Thread): #一定要继承Thread类并且重写run方法 def __init__(self,name): threading.Thread.__init__(self) #一定要显示的调用父类的构造函数 self.name = name def run(self): #每个线程要运行的函数 print("my name is ",self.name) time.sleep(3) if __name__ == "__main__": t1 = MyThread("zhangyan") t2 = MyThread("spring") t1.start() t2.start()
构造方法:
Thread(group=None, target=None, name=None, args=(), kwargs={})
group: 线程组,目前还没有实现,库引用中提示必须是None;
target: 要执行的方法;
name: 线程名; (可以在这里给线程起名字,也可以使用setName()方法设置名字)
args/kwargs: 要传入方法的参数。
更多方法:
start:线程准备就绪,等待CPU调度
setName:为线程设置名称
getName:获取线程名称
setDaemon:设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止,
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止。
join:等待子线程运行结束
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time def hello(name): print("start...",name) time.sleep(3) print("end...",name) if __name__ == "__main__": t1 = threading.Thread(target=hello,args=("zhangyan",)) #生成一个线程 t2 = threading.Thread(target=hello,args=("spring",)) #生成另一个线程 t1.setDaemon(False) #设置一定要设置在start之前,默认就是False,不写也行 t2.setDaemon(False) t1.start() #启动t1 t2.start() #启动t2 print("main thread end!") #主线程运行结束 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #运行结果 start... zhangyan start... spring main thread end! end... zhangyan end... spring
多线程设置为前台线程,主线程结束程序并没有停止,而是其他线程也结束后才停止。
setDaemon()一定要设置在start()之前
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time def hello(name): print("start...",name) time.sleep(3) print("end...",name) if __name__ == "__main__": t1 = threading.Thread(target=hello,args=("zhangyan",)) #生成一个线程 t2 = threading.Thread(target=hello,args=("spring",)) #生成另一个线程 t1.setDaemon(True) #设置为后台线程 t2.setDaemon(True) t1.start() #启动t1 t2.start() #启动t2 print("main thread end!") #主线程结束 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #运行结果 start... zhangyan start... spring main thread end!
多线程设置为后台线程,主线程结束后程序立刻停止,未等到后台线程运行结束。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time def hello(name): print("start...",name) time.sleep(3) print("end...",name) if __name__ == "__main__": thread_list = [] #创建线程存储的列表 for i in range(4): t = threading.Thread(target=hello,args=str(i)) t.setDaemon(True) thread_list.append(t) for t in thread_list: t.start() for t in thread_list: t.join() print("main thread end!") >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #运行结果 start... 0 start... 1 start... 2 start... 3 end... 3 end... 2 end... 1 end... 0 main thread end!
也就是说,只有在多线程设置为后台线程时候,join才有用武之地。如果多线程设置为前台线程(未设置也是前台线程,默认的),程序本身就会等待全部线程运行结束后才停止,不需要join。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time def hello(name): print("start...",name) time.sleep(3) print("end...",name) if __name__ == "__main__": for i in range(4): t = threading.Thread(target=hello,args=str(i)) t.setDaemon(True) t.start() t.join() print("main thread end!") >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start... 0 end... 0 start... 1 end... 1 start... 2 end... 2 start... 3 end... 3 main thread end!
也不能说是为错误用法,只是这个用法使得多线程又变成了单线程,使得多线程变得毫无意义。
创建每个线程的时候被设置了join,下一个线程就会等待这个线程结束之后才创建。
这也是之前的例子中需要使用thread_list来存储线程的原因,因为创建完毕之后还需要遍历一遍设置个join。
二、线程锁(Lock、RLock)
试想一下,多线程操作的时候是否可能会发生如下的情况。
在某一条线程对数据进行+1操作的时候,刚获取到数据还未进行+1(n条指令执行完毕),这时候CPU切换到了另一条线程上,这个线程也同样进行+1的操作。假如说原数据是3,那么这两个线程交给CPU运算的数据都是3,运算结束后变成了4,而不是设想中的5。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time gl_num = 0 def show(arg): global gl_num time.sleep(1) gl_num +=1 print(gl_num) for i in range(10): t = threading.Thread(target=show, args=(i,)) t.start() print('main thread stop')
多次运行可能产生混乱,为了多个线程同时操作一个内存中的资源时不产生混乱,我们使用锁。
(python3中好像默认就使用了锁,所以在使用上述的例子的时候不会产生混乱。)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading,time gl_num = 0 def show(arg): global gl_num time.sleep(1) lock.acquire() gl_num +=1 lock.release() print(gl_num) if __name__ == "__main__": lock = threading.Lock() for i in range(10): t = threading.Thread(target=show, args=(i,)) t.start() print('main thread stop')
RLock中除了状态locked和unlocked外还记录了当前lock的owner和递归层数,使得RLock可以被同一个线程多次acquire()。
三、信号量(Semaphore)
线程锁(互斥锁)同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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(5) # 最多允许5个线程同时运行 for i in range(20): t = threading.Thread(target=run, args=(i,)) t.start()
四、事件(event)
事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
- clear:将“Flag”设置为False
- set:将“Flag”设置为True
- isset:当内置标志为True时返回True。
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()
五、Timer
Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。
Timer(interval, function, args=[], kwargs={})
interval: 指定的时间
function: 要执行的方法
args/kwargs: 方法的参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading def func(): print 'hello timer!' timer = threading.Timer(5, func) timer.start()