16-扩展:全局锁、递归锁等
GIL¶
python 的解释器,cpython, jpython, pypy 有很多种,但市场占有率99.9%的都是基于c语言编写的CPython. 在这个解释器里规定了GIL。
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
无论有多少个cpu,python在执行时会在同一时刻只允许一个线程运行。Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作CPU,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。
建议:
python下想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,在python中,有多核CPU情况下,多进程的执行效率优于多线程。
import threading, multiprocessing
def loop():
x = 0
while True:
x = x ^ 1
if __name__ == '__main__':
count = multiprocessing.cpu_count()
print("CPU:", count)
for i in range(count):
t = threading.Thread(target=loop)
t.start()
# for i in range(4):
# t = multiprocessing.Process(target=loop)
# t.start()
互斥锁(mutex)¶
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
- or
for i in range(100000):
# 先要获取锁:
with lock:
try:
# 放心地改吧:
change_it(n)
except Exception as e:
print("Exception: " , e)
递归锁(RLock)¶
- 需求
import threading, time
class myThread(threading.Thread):
def doA(self):
lockA.acquire()
print(self.name, "gotlockA", time.ctime())
time.sleep(3)
lockB.acquire()
print(self.name, "gotlockB", time.ctime())
lockB.release()
lockA.release()
def doB(self):
lockB.acquire()
print(self.name, "gotlockB", time.ctime())
time.sleep(2)
lockA.acquire()
print(self.name, "gotlockA", time.ctime())
lockA.release()
lockB.release()
def run(self):
self.doA()
self.doB()
if __name__ == "__main__":
lockA = threading.Lock()
lockB = threading.Lock()
threads = []
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
当某个线程申请到一个锁,其余线程不能再申请。于是有了递归锁(其实就是内部维护了一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。)
- 解决
import threading,time
class myThread(threading.Thread):
def doA(self):
r_lock.acquire()
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
r_lock.acquire()
print(self.name,"gotlockB",time.ctime())
r_lock.release()
r_lock.release()
def doB(self):
r_lock.acquire()
print(self.name,"gotlockB",time.ctime())
time.sleep(2)
r_lock.acquire()
print(self.name,"gotlockA",time.ctime())
r_lock.release()
r_lock.release()
def run(self):
self.doA()
self.doB()
if __name__=="__main__":
r_lock=threading.RLock()
threads=[]
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
线程本地变量(ThreadLocal)¶
多函数共享变量
import threading
local_class = threading.local()
def show_score():
name = local_class.student
score = local_class.score
print("Hello {}, 总分为 {}".format(name, score))
def handle_student(name,score):
local_class.student = name
local_class.score = score
show_score()
t1=threading.Thread(target=handle_student, args=("赵四",80))
t2=threading.Thread(target=handle_student, args=("刘能",99))
t1.start()
t2.start()
t1.join()
t2.join()
定时器(Timer)¶
from threading import Timer
def hello():
print("hello, world")
t = Timer(3, hello)
# after 1 seconds, "hello, world" will be printed
t.start()
线程池(ThreadPoolExecutor)¶
from concurrent.futures import ThreadPoolExecutor
import time
def sayhello(a):
print("hello: ", a)
time.sleep(2)
if __name__ == '__main__':
seed = range(10)
with ThreadPoolExecutor(3) as executor:
for each in seed:
executor.submit(sayhello, each)
# with ThreadPoolExecutor(3) as executor1:
# executor1.map(sayhello, seed)