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()

深入理解Python中的ThreadLocal变量(上)

深入理解Python中的ThreadLocal变量(下)

定时器(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)