05线程2 GIL与Lock等

一、GIL(Global Interpreter Lock:全局解释器锁)
每次执行python程序,都会产生一个独立的进程。在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内。
某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是“通行证”,并且在一个 Python 进程中,GIL 只有一个。
拿不到通行证的线程,就不允许进入 CPU 执行。
全局解释锁(GIL):就是一个互斥体,只允许一个线程来控制Python解释器。

二、 GIL与Lock

GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理

需要注意的点:
1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,
其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,
要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

例:多线程共享数据没有加锁
from threading import Thread
import time

def task():
    global n
    temp = n
    time.sleep(0.1)
    n = temp - 1

if __name__ == '__main__':
    n = 100
    l = []
    for i in range(100):
        t = Thread(target=task,)
        l.append(t)
        t.start()
    for t in l:
        t.join()

    print('', n) #结果为99,因为创建线程速度很快,数据没有加锁,每个线程拿到的n都是100

 

加锁解决数据共享问题,方式一
from threading import Thread,Lock
import time
def task():
    global n
    lock.acquire() #加锁,也可以用这种方式:with mutex:#加锁
    temp = n
    time.sleep(0.1)
    n = temp - 1
    lock.release() #释放锁

if __name__ == '__main__':
    lock = Lock()
    n = 100
    l = []
    for i in range(100):
        t = Thread(target=task)
        l.append(t)
        t.start()
    for t in l:
        t.join()

    print('', n) #结果为0
方式二:这种方式:with mutex:#加锁
from threading import Thread, Lock
import time

n = 100
def task():
    global n
    with mutex:#加锁,因为线程共用的一个进程的资源,因此,用的都是同一把锁,不用把锁作为参数传进去了
        temp = n
        time.sleep(0.1)
        n = temp -1

if __name__ == '__main__':
    mutex = Lock()
    t_l = []
    for i in range(100):
        t = Thread(target=task)
        t_l.append(t)
        t.start()

    for t in t_l:
        t.join()#保证所有的子线程都执行完毕

    print('', n)

 三、死锁与递归锁

死锁:指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。

解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁
from threading import Thread, Lock, RLock
import time

# mutexA = Lock()
# mutexB = Lock()
mutexA = mutexB = RLock()

class MyThead(Thread):
    def run(self):
        self.fun1()
        self.fun2()

    def fun1(self):
        mutexA.acquire()
        print('{}拿到A锁'.format(self.name))

        mutexB.acquire()
        print('{}拿到B锁'.format(self.name))
        mutexB.release()

        mutexA.release()

    def fun2(self):
        mutexB.acquire()
        print('{}拿到B锁'.format(self.name))
        time.sleep(1)

        mutexA.acquire()
        print('{}拿到A锁'.format(self.name))
        mutexA.release()

        mutexB.release()


if __name__ == '__main__':
    for i in range(10):
        t = MyThead()
        t.start()

 四、信号量

from threading import Thread, Semaphore, current_thread
import time, random

sm = Semaphore(5) #信号量设为5,同时最大连接数为5,同时只有5个线程可以得到Semaphore

def task():
    with sm:
        print('{} is running'.format(current_thread().getName()))
        time.sleep(random.randint(1,3))
        print('{} is done'.format(current_thread().getName()))

if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=task)
        t.start()

 五、Event

对象包含一个可由线程设置的信号标志, 它允许线程等待某些事件的发生。

event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。

  例1:

# (1)最简单的版本
from threading import Thread, Event, current_thread
import time

e = Event()

def check():
    print('{} is checking'.format(current_thread().getName()))
    time.sleep(3)
    e.set()

def conn():
    print('{} is wait to connect Mysql'.format(current_thread().getName()))
    e.wait()
    print('{} connected Mysql'.format(current_thread().getName()))

if __name__ == '__main__':
    t1 = Thread(target=check)
    t2 = Thread(target=conn)
    t3 = Thread(target=conn,)
    t4 = Thread(target=conn,)

    t1.start()
    t2.start()
    t3.start()
    t4.start()

# (2)设置超时时间,最大连接次数
from threading import Thread, Event, current_thread
import time

e = Event()
def check():
    print('{} is checking'.format(current_thread().getName()))
    time.sleep(0.1)
    e.set()

def conn():
    count = 1 #技术,连接次数
    while not e.is_set():
        if count > 3: #如果三次连接失败,则超时
            raise TimeoutError('超时')
        print('{} is wait to connect Mysql'.format(current_thread().getName()))
        e.wait(1) # 等待时间
        count += 1

    print('{} connected Mysql'.format(current_thread().getName()))

if __name__ == '__main__':
    t1 = Thread(target=check)
    t2 = Thread(target=conn)
    t3 = Thread(target=conn,)
    t4 = Thread(target=conn,)

    t1.start()
    t2.start()
    t3.start()
    t4.start()

# (3)设置超时时间,最大连接次数,循环的另一个写法
from threading import Thread, Event, current_thread
import time

e = Event()
def check():
    print('{} is checking'.format(current_thread().getName()))
    # time.sleep(0.0001)
    e.set()

def conn():
    count = 1#计数,连接次数
    while count < 4:
        print('{} is wait to connect Mysql'.format(current_thread().getName()))#这两句必须要放前面
        e.wait(1)  # 等待时间
        if e.is_set():#此时e为TRUE,说明可以连接
            print('{} connected Mysql'.format(current_thread().getName()))
            break
        count += 1
    else:
        raise TimeoutError('超时')


if __name__ == '__main__':
    t1 = Thread(target=check)
    t2 = Thread(target=conn)
    t3 = Thread(target=conn,)
    t4 = Thread(target=conn,)

    t1.start()
    t2.start()
    t3.start()
    t4.start()

 例2:红绿灯

import time, random
from threading import Thread, Event, current_thread

e = Event()

def f1():
    while True:
        e.clear()
        print('红灯,等2秒')
        time.sleep(2)

        e.set()
        print('绿灯,持续2秒')
        time.sleep(2)

def f2():
    while True:
        if e.is_set():
            print('{} 过马路'.format(current_thread().getName()))
            break
        else:
            print('{}等待'.format(current_thread().getName()))
            e.wait()

if __name__ == '__main__':
    Thread(target=f1).start()
    while True:
        time.sleep(random.randint(1,5))
        Thread(target=f2).start()

 六、定时器:指定n秒后执行

from threading import Timer

def hello(name):
    print('hello {}'.format(name))

t = Timer(1, hello, args=('cc',)) #指定1秒后执行
t.start()
七、线程Queue
queue队列 :使用import queue,用法与进程Queue一样
# (1)q=queue.Queue()队列:先进先出
import queue

q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())

# (2)q = queue.LifoQueue(),堆栈,后进先出
import queue
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())

# (3)q = queue.PriorityQueue(),优先级队列
import queue
q = queue.PriorityQueue()
q.put((20, 1))
q.put((30, 2))
q.put((10, 3))

print(q.get())
print(q.get())
print(q.get())

 

posted @ 2021-03-01 16:06  cheng4632  阅读(101)  评论(0编辑  收藏  举报