day36网络编程——threading模块线程
进程和线程的关系,一个进程内可以含有很多的线程,在Cpython里面,每个进程内多个线程只能用一个core去执行,这样为什么在学了进程之后为什么会学线程的原因。以后可以根据需求去编制多线程的代码和多进程的代码。
1、同一进程内的多个线程是共享该进程的资源
2、创建新的线程开销要远远小于开启新的进程
from threading import Thread import os,time def work(): print("%s is running"%os.getpid()) if __name__=="__main__": for i in range(10): t1 = Thread(target= work) t1.start() print("主线程",os.getpid())
from threading import Thread a = 100 def work(): global a a =0 if __name__ =="__main__": t = Thread(target=work) t.start() print(a)
线程对象的其他方法
Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
from threading import Thread import threading from multiprocessing import Process import os def work(): import time time.sleep(3) print(threading.current_thread().getName()) if __name__ == '__main__': #在主进程下开启线程 t=Thread(target=work) t.start() print(threading.current_thread().getName()) print(threading.current_thread()) #主线程 print(threading.enumerate()) #连同主线程在内有两个运行的线程 print(threading.active_count()) print('主线程/主进程') ''' 打印结果: MainThread <_MainThread(MainThread, started 140735268892672)> [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>] 主线程/主进程 Thread-1 '''
线程守护
线程守护用法和进程守护的区别:
守护进程在主进程代码执行完毕后也会停止执行,但是主程序不会结束,直到其他非守护子进程执行完毕,为子进程收尸。(随主)
守护线程在主线程代码执行完毕后也不会停止执行,而是等到其他非守护线程的执行完毕而停止执行。(随非守护)
GIL 全局解释器锁
三个需要注意的点: #1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来 #2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高 #3. 一定要看本小节最后的GIL与互斥锁的经典分析
GIL VS Lock
机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?
首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
然后,我们可以得出结论:保护不同的数据就应该加不同的锁。
最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock
过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限
线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果
既然是串行,那我们执行
t1.start()
t1.join
t2.start()
t2.join()
这也是串行执行啊,为何还要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。
from threading import Thread from multiprocessing import Lock import time n =100 def work(): global n with lock: temp = n time.sleep(0.01) n -=1 if __name__ == '__main__': lock = Lock() t_l =[] for i in range(100): t = Thread(target=work) t_l.append(t) t.start() for i in t_l: i.join() print("主线程",n)
基于多线程的并发,cpython解释器不允许并行的这用情况,我们可以根据具体需要选择使用多进程和多线程。
多线程的优势在于创建新的线程开销要远远小于开启新的进程。所以能使用多线程就去尽量使用。
如果是i/o操作较多的时候就尽量使用开线程的方式,比起开进程省去了创建新进程的时间。
如果是纯计算操作较多的时候尽量使用开进程的方式,多核操作比起多线程的串行操作省很多时间。
from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] start=time.time() for i in range(400): # p=Process(target=work) # 5.158523797988892 p=Thread(target=work) #20.0011146068573 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
from multiprocessing import Process from threading import Thread import os,time def work(): time.sleep(2) if __name__ == '__main__': l=[] start=time.time() for i in range(4): p=Process(target=work) # 2.4321393966674805 # p=Thread(target=work) #2.0011146068573 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))

浙公网安备 33010602011771号