一,线程Thread模块
1.效率更高(相对于进程)
import time from multiprocessing import Process from threading import Thread def func(a): a=a+1 if __name__=="__main__": start=time.time() t_l=[] for i in range(50): t=Thread(target=func,args=(i,)) t_l.append(t) t.start() for t in t_l: t.join() print("主线程结束") print(time.time()-start) # 0.010993003845214844 start=time.time() p_l=[] for i in range(50): p=Process(target=func,args=(i,)) p_l.append(p) p.start() for p in p_l: p.join() print("主进程") print(time.time()-start) # 6.235144376754761
2.线程之间的数据共享 (进程中数据是隔离的):
from threading import Thread n=100 def func(): global n #子线程引用主线程的变量 (进程中不可以引用) n=n-1 #并修改主线程的变量值 t_l=[] for i in range(100): t=Thread(target=func) t_l.append(t) t.start() for t in t_l: t.join() print(n) # 0
3.守护线程
import time from threading import Thread def func1(): while True: print("True") #4.打印True 11.** 13.** time.sleep(0.5) # 5.睡1 秒 #12.又休1秒 14.又休1秒 def func2(): print("in f2 start") #8打印in f2 start time.sleep(3) #9,休3秒 print("in f2 end") #15打印 in f2 end t1=Thread(target=func1) #1.创建一个线程 t1.setDaemon(True) #2.设置成守护进程 t1.start() #3.申请开启线程 t2=Thread(target=func2) #6.创建另一个线程 t2.start() #7.申请开启另一个线程 time.sleep(1) # 12.** print('主线程') #10. 打印主线程总结:主线程如果结束了,那么整个进程就结束,所有线程就结束
守护进程:只守护主进程的代码就可以了,守护进程会在主进程的代码执行完毕之后直接结束,无论守护进程是否执行完毕
4.开启线程的第二种方式:
import time from threading import Thread,get_ident class MyThread(Thread): def __init__(self,args): super().__init__() self.args=args def run(self): print("in my thread:" ,get_ident(), self.args) print("in main",get_ident()) # in main 11676 m=MyThread("hahaha") m.start() # in my thread: 22100 hahaha
5.Thread类的其他方法
Thread实例对象的方法:
isAlive():返回线程是否执行
getName():返回线程名
setName():设置线程名
threading模块提供的一些方法:
threading.currentThread():返回当前的线程变量
threading.enumerate():返回一个包含正在运行的线程的list,正在运行指线程启动后、结束前,不包括启动前和终止后的线程
threading.activeCount() :返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
示例:
import time import threading from threading import Thread,get_ident,currentThread print(threading.currentThread().getName()) # MainThrea print(threading.enumerate()) #[<_MainThread(MainThread, started 9780)>] print(threading.activeCount()) #1
二.线程中的锁 Lock
1. 什么时候用锁:
在多个进程\线程同时访问一个数据的时候就会产生数据的不安全的现象
2.区别GIL 全局解释器锁
在同一个进程里的每一个线程同一时间只能有一个线程访问CPU
3.尽量不要设置全局变量
只要在多线程/进程之间用到全局变量,就加上锁
第一版:
import time import threading from threading import Thread,get_ident,currentThread print(threading.currentThread().getName()) # MainThrea print(threading.enumerate()) #[<_MainThread(MainThread, started 9780)>] print(threading.activeCount()) #1
第二版:科学家吃面:
1.死锁现象:
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,
它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
import time from threading import Thread,Lock lock=Lock() noodle_lock=Lock() fork_lock=Lock() def eat1(name): noodle_lock.acquire() print("%s拿到了面" % name) fork_lock.acquire() print("%s拿到了叉子"%name) print("%s在吃面"%name) time.sleep(0.5) fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print("%s拿到了叉子"%name) noodle_lock.acquire() print("%s拿到面条"%name) print("%s在吃面"%name) time.sleep(0.5) noodle_lock.release() fork_lock.release() eat_l=["alex","wusir","太白","苑"] for name in eat_l: Thread(target=eat1,args=(name,)).start() Thread(target=eat2,args=(name,)).start()
2.递归锁(Rlock):
递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一
个线程所有的acquire都被release,其他的线程才能获得资源
3.用递归锁解决上述死锁问题:
import time from threading import Thread,RLock lock=RLock() def eat1(name): lock.acquire() print("%s拿到了面" % name) lock.acquire() print("%s拿到了叉子"%name) print("%s在吃面"%name) time.sleep(0.5) lock.release() lock.release() def eat2(name): lock.acquire() print("%s拿到了叉子"%name) lock.acquire() print("%s拿到面条"%name) print("%s在吃面"%name) time.sleep(0.5) lock.release() lock.release() eat_l=["alex","wusir","太白","苑"] for name in eat_l: Thread(target=eat1,args=(name,)).start() Thread(target=eat2,args=(name,)).start()
4.互斥锁解决死锁问题
import time from threading import Thread,Lock lock=Lock() def eat1(name): lock.acquire() print("%s拿到了面" % name) print("%s拿到了叉子"%name) print("%s在吃面"%name) time.sleep(0.5) lock.release() def eat2(name): lock.acquire() print("%s拿到了叉子"%name) print("%s拿到面条"%name) print("%s在吃面"%name) time.sleep(0.5) lock.release() eat_l=["alex","wusir","太白","苑"] for name in eat_l: Thread(target=eat1,args=(name,)).start() Thread(target=eat2,args=(name,)).start()
面试题:
互斥锁和递归锁哪个好:
递归锁 快速恢复服务
死锁问题的出现是程序的设计或者逻辑的问题
还应该进一步的排除和重构逻辑来保证使用互斥锁也不会发生死锁
互斥锁与递归锁的区别:
互斥锁:就是在一个线程中不能连续多次acquire
递归锁:可以在同一个线程中acquire任意次,注意acquire多少次就需要release多少次
三.线程中的信号量 Semaphore
信号量: 锁 + 计数器
信号量 VS 进程池
import time from multiprocessing import Semaphore,Process,Pool def ktv1(sem,i): sem.acquire() i+=1 sem.release() def ktv2(i): i+=1 if __name__=="__main__": #信号量 sem=Semaphore(5) start=time.time() p_l=[] for i in range(100): p=Process(target=ktv1,args=(sem,i)) p.start() p_l.append(p) for p in p_l: p.join() print("###",time.time()-start) # 7.551328897476196 #进程池 start=time.time() p = Pool(5) for i in range(100): ret=p.apply_async(func=ktv2,args=(p,i)) p.close() p.join() print("@@@",time.time()-start) #@@@ 1.013512372970581
Thread信号量:
import time from threading import Semaphore,Thread def ktv1(sem, i): sem.acquire() i += 1 sem.release() def ktv2(i): i += 1 if __name__ == "__main__": sem = Semaphore(5) start = time.time() p_l = [] for i in range(100): t = Thread(target=ktv1, args=(sem, i)) t.start() p_l.append(t) for t in p_l: t.join() print("###", time.time() - start) # ### 0.01998734474182129
总结:
进程池
进程池的效率更高;
池子里有几个就起几个
不管多少任务,池子的个数是固定的
开启进程和关闭进程这些事都是需要固定的开销的
不产生额外的时间开销
信号量:
有多少任务就起多少进程/线程
可以帮助你减少操作系统切换的负担
但是并不能帮助你减少进程/线程开启和关闭的时间
四.线程中的事件 Event
事件四个方法:
wait : 等到事件内部的信号变成True就不阻塞了,
set: 设置信号为True
clear : 设置信号变成False
is_set : 查看信号是否为True
示例:
import time import random from threading import Thread,Event def check(e): print("正在检测两台机器之间的网络情况") time.sleep(random.randint(0,2)) e.set() def connect_db(e): n = 0 while n < 3: if e.is_set(): break else: e.wait(0.5) n += 1 if n == 3: raise TimeoutError print('连接数据库 ... ') print('连接数据库成功~~~') e=Event() Thread(target=connect_db,args=(e,)).start() Thread(target=check,args=(e,)).start()
五.条件 Condition
condition :
Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个
条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,
其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。
condition 的相关方法
acquire
release
wait 阻塞
notify 让wait解除阻塞的工具
在执行wait和notify这两个方法的前后 必须执行acquire和release
from threading import Condition,Thread def func(con,i): con.acquire() con.wait() print("threaing:",i) con.release() con=Condition() for i in range(20): Thread(target=func,args=(con,i)).start() con.acquire() con.notify_all() con.release() while True: num=int(input("num>>>")) con.acquire() con.notify(num) con.release()
六.定时器 Timer
指定n秒后执行某个操作
from threading import Timer def func(): print("in func,执行吧") Timer(1,func).start()
七.多线程起socket(tcp)
服务器代码
import socket import os sk=socket.socket() sk.connect(("127.0.0.1",9991)) while True: print(sk.recv(1024)) sk.send(str(os.getpid()).encode('utf-8')) sk.close()
客户端代码
import socket import os sk=socket.socket() sk.connect(("127.0.0.1",9991)) while True: print(sk.recv(1024)) sk.send(str(os.getpid()).encode('utf-8')) sk.close()