NO.3 Python多线程&锁
零蚀
线程
线程CPU独立调度和分配的基本单位。线程和进程共享应用的所有的资源。
- 线程编码
import threading
def say():
print("shout")
time.sleep(0.5)
if __name__ == "__main__":
for i in range(20):
thread = threading.Thread(target=say)
thread.start()
⚠️ **exit()**只能退出进程,并不能关闭进程运行的子线程。
-
线程API
- 获取活线程对象列表
def say(): print("shout") time.sleep(10) def thread_api(): while True: print(threading.enumerate()) print(len(threading.enumerate())) time.sleep(1) if __name__ == "__main__": for i in range(3): thread = threading.Thread(target=say) thread.start() thread_api()
[<_MainThread(MainThread, started 4714063296)>, <Thread(Thread-1, started 123145492938752)>, <Thread(Thread-2, started 123145498193920)>, <Thread(Thread-3, started 123145503449088)>] 4
- 获取活线程数量
#threading.activeCount() threading.active_count()
- 参数传递
''' 元祖 ''' def say(i): print("shout",i) time.sleep(10) thread = threading.Thread(target=say,args=(18,)) thread.start() ''' 字典 ''' def say(i): print("shout", i) time.sleep(10) thread = threading.Thread(target=say, kwargs={"i": 11}) thread.start() # 也可以字典+元祖
当主线程结束需要结束子线程的所有内容。执行exit()时子线程也全部退出,需要将子线程转变为守护线程,此方法必须在start之前:
thread = threading.Thread(target=say, kwargs={"i": 11}) # 设置为守护线程 thread.setDaemon(True) thread.start()
- 自定义线程
class MyThread(threading.Thread): # 传参 def __init__(self, num): super().__init__() self.num = num if __name__ == "__main__": thread = MyThread() thread.start()
run函数内部本来是运行target指定的函数,但是重写了此函数,target就不会再执行了。
- 共享全局变量
def work1(): global num for i in range(0, 100): num += 1 print("num:",num) print("work1") def work2(): global num for i in range(0, 100): num -= 1 print("num1:", num) if __name__=="__main__": thread1=threading.Thread(target=work1) threading.Thread(target=work2).start() thread1.start() # 阻塞当前线程(在主线程里),知道线程执行结束,才执行主线程 thread1.join() print("main")
**join()**阻塞当前线程(在主线程里),知道线程执行结束,才执行主线程。
- 线程问题
def work1(): global num for i in range(0, 1000000): num += 1 print("work1:", num) def work2(): global num for i in range(0, 1000000): num += 1 print("work2:", num) if __name__ == "__main__": thread1 = threading.Thread(target=work1) thread2 = threading.Thread(target=work2) thread1.start() thread2.start() thread1.join() thread2.join() print("main:", num) #print #work1: 1313994 #work2: 1523812 #main: 1523812
按道理说会是200,但是这里加到最后并不是200。
CPU执行单元原理:
在大规模的全局变量运算中,会有很大的概率,导致两个运算交叉。
线程锁
- 互斥锁
将线程完全隔离,每次只保证一个线程在正常运作,
if __name__ == "__main__":
thread1 = threading.Thread(target=work1)
thread2 = threading.Thread(target=work2)
# 创建锁
lock = threading.Lock()
# 锁定
lock.acquire()
thread1.start()
thread1.join()
# 释放
lock.release()
thread2.start()
thread2.join()
print("main:", num)
这里互斥锁将**lock.acquire()和ock.release()**之间的内容进行锁定,使得其中的内容在运行时不被影响,其他线程只能被阻塞在原地。但是互斥锁是比较消耗时间的。
- 死锁
当锁没有释放前发生了bug程序不能继续进行,而程序的互斥锁一直没有释放,所以程序会一直卡在锁里。lock的简洁写法如下,但是有异常还要自己释放。
# 创建锁
lock = threading.Lock()
with lock:
pass
# 自己释放锁
GIL锁
python里面的一个进程只有一个CPU为其服务,python只有进程才能为应用获取系统资源。无论多少CPU,python执行的时候一个CPU有且仅有一个被允许运行一个进程。每个进程都有自己独立的GIL且互不干扰。只能利用多进程进行并行操作。
- 多进程
import multiprocessing
def func():
pass
process = multiprocessing.Process(target=func)
process.start()
递归锁
情况如下:
import threading
import time
def A(i):
with lockA:
print("获取到了A:", i)
time.sleep(3)
with lockB:
print("A想获取B的lock",i)
def B(i):
with lockB:
print("获取到了B:", i)
time.sleep(2)
with lockA:
print("B想获取A的lock",i)
class Mythread(threading.Thread):
def __init__(self, index):
super().__init__()
self.index = index
def run(self):
A(self.index)
B(self.index)
if __name__ == '__main__':
lockA = threading.Lock()
lockB = threading.Lock()
for i in range(0, 5):
newThread = Mythread(i)
newThread.start()
A锁里面想拿B锁,B锁里面想拿A锁,互相拿锁过程中A()想拿BLock且没释放ALock,B()想拿ALock且没释放BLock,就形成了互掐的僵局,在这种锁里面套着锁定情况下,需要用递归锁。
递归锁里面有一个account参数,同一个线程里有可以acqurie多次申请一次同类,account+1,释放时候account-1,这样避免了一次的aqurie导致堵塞。
import threading
import time
def A(i):
with rlock:
print("获取到了A:", i)
time.sleep(3)
with rlock:
print("A想获取B的lock",i)
def B(i):
with rlock:
print("获取到了B:", i)
time.sleep(2)
with rlock:
print("B想获取A的lock",i)
class Mythread(threading.Thread):
def __init__(self, index):
super().__init__()
self.index = index
def run(self):
A(self.index)
B(self.index)
if __name__ == '__main__':
rlock = threading.RLock()
for i in range(0, 5):
newThread = Mythread(i)
newThread.start()
事件EVENT
event = threading.Event()
event有以下的方法:
方法 | 注释 |
---|---|
clear | 将flag设置为False |
set | 将flag设置为True(无参) |
is_set | 判断是否设置了flag |
wait | 会一直监听flag,如果没有检测到flag为True就一直处于阻塞的状态 |
Timer
方法和thread一致
from threading import Timer
def work(i):
print(i)
Timer(3, function=work,args=(1,)).start()
线程池(Python3.x)
线程池中会有队列,如果有新的任务线程过来,会进行任务排队,这样防止过多的创建线程,导致CPU卡死,和切换线程的巨大开销。在有限的线程中不断切换,可以节约内存。
from concurrent.futures import ThreadPoolExecutor
def say(str):
print("say",str)
if __name__ == '__main__':
with ThreadPoolExecutor(3) as pool:
for i in range(6):
pool.submit(say,"666")
# pool.map(say,[1,2,3,4,5,6])
🔗 前言
🔗 Python 高级列表
🔗 NO.1 Python网络通信
🔗 NO.2 Python Web 服务器
🔗 NO.4 Python多进程
🔗 NO.5 Python协程&生成器
🔗 NO.6 Python正则&爬虫初
🔗 NO.7 Python操作MySQL
🔗 NO.8 Python Spider
🔗 NO.9 Python 装饰器&other