多线程(threading)
多线程与多进程其实大同小异,他们有很多方法从名字到功能都是一样,比如都有start(),join(),都有守护线程/进程deamon.
一个简单的栗子:
import threading import os,time def loop(): for i in range(5): t = threading.Thread(name=str(i)) t.start() print('thread %s is running!'%t.name) print('thread %s exited!'%t.name) t.join() if __name__ == "__main__": print('thread %s is running!'%threading.current_thread().name) T = threading.Thread(target=loop) T.start() T.join() print('thread %s exited'%threading.current_thread().name)
output:
由于Python的多线程并不是真正意义上充分利用多核性能的多线程,它是只是实现了单核并发(详见并发与并行),也就是说,处理多线程时,
每个线程执行一部分代码,然后执行下一个线程的代码,所以稍不留神,输出就会跟设计的初衷相左.
import threading import time num = 0 def change(n): #声明全局变量num global num #进行加减n num = num + n num = num - n def run_thread(n): #调用change方法100万次 for i in range(1000000): change(n) if __name__ == "__main__": t1 = threading.Thread(target=run_thread,args=(5,)) t2 = threading.Thread(target=run_thread,args=(8,)) t1.start() t2.start() t1.join() t2.join() print(num)
output:
第一次:
第二次:
第三次:
会得出三次不同的结果,事实上,如果增大调用change的次数,结果会更显著.
原因是多线程间的变量是共享的.num在被线程t1,t2交替使用.
语句num = num + n其实分作两步:
先计算num+n的值存入临时变量
然后把临时变量赋值给num
但在多线程中,尤其是运算量大的地方,并不能保证这两步的连贯执行,因此就不能保证结果的准确性.
因此我们需要引入多线程特有的类:锁!
Lock
Lock类只有两个方法:
acquire
(blocking=True, timeout=-1)
这是一个等待锁的方法,可选参数也是我们的老朋友,堵塞与超时
release
()释放锁的方法,一个函数获取了锁之后一定要释放锁,否则还在等待锁的程序望穿眼也等不到锁,成为幽灵线程.
所以,上述的代码要这样优化:
import threading import time num = 0 lock = threading.Lock() def change(n): #声明全局变量num global num #进行加减n num = num + n num = num - n def run_thread(n): #调用change方法100万次 for i in range(1000000): #获取锁 lock.acquire() try: change(n) finally: #最后必定要释放锁 lock.release() if __name__ == "__main__": t1 = threading.Thread(target=run_thread,args=(5,)) t2 = threading.Thread(target=run_thread,args=(8,)) t1.start() t2.start() t1.join() t2.join() print(num)
这样无论怎么如何,结果都错不了了!
最后的最后,由于Python GIL(全局锁)的存在,任何Python线程执行前,线程都要获得GIL锁,然后没执行100条字节码,自动释放GIL锁,然后去执行其他的线程任务,
所以Python的多线程并不能利用多核.所以要想利用多核,还是好好用多进程吧!
Local:
threading库下的local类,实现了各线程间的数据独立.
import threading #创建全局local对象 local = threading.local() def set_std(): #获取与当前线程关联的student std = local.student print('I am %s in thread %s'%(std,threading.current_thread().name))
#在这个函数准备好后面需要的参数,如本例中的student def set_thr(name): local.student = name set_std() if __name__ == "__main__": t1 = threading.Thread(target=set_thr,args=("你爹",),name="No.1") t2 = threading.Thread(target=set_thr,args=("你妈",),name="No.2") t1.start() t2.start() t1.join() t2.join()
output:
文章参考了廖雪峰廖大的教程,感谢廖大!