线程相关 GIL queue event 死锁与递归锁 信号量l

#一、GIL全局解释器锁

GIl是一个互斥锁:保证数据的安全(以牺牲效率来换取数据的安全)
阻止同一个进程内多个线程同时执行(不能并行但能实现并发)
并发:看起来像同时进行的)
GIL全局解释器存在的原因是因为Cpython解释器的内存管理不是线程安全的

垃圾回收机制:  (可以作为一种线程)
    1、引用计数
    2、标记清除
    3、分带回收

同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行

1、存在四个任务:计算密集型的任务 每个耗时10s
    单核情况下:多线程好一点,消耗的资源较少
    多核情况下:
        开四个进程:10s多一些
        一个进程下开启四个线程:40多秒
2、存在四个任务:IO密集型的任务 每个任务IO 10s
    单核情况下:多线程好一些
    多核情况下:多线程好一些

  

#计算密集与IO密集情况下线程与进程的耗时比较

#计算密集型

from multiprocessing import Process
from threading import Thread
import os,time

def work():
    res = 0
    for i in range(12345678):
        res*=i

if __name__ == '__main__':
    l = []
    print(os.cpu_count())  #查看cpu核数
    start_time = time.time()
    for i in range(4):   #4个进程或者4个线程去计算
        # p = Process(target=work) #run time is 3.224184513092041
        p = Thread(target=work)   #run time is 6.93839693069458
        l.append(p)
        p.start()

    for p in l:
        p.join()
    stop_time = time.time()
    print('run time is %s'%(stop_time-start_time))


#IO密集型

from multiprocessing import Process
from threading import Thread
import time

def work():
    time.sleep(2)

if __name__ == '__main__':
    l = []
    start_time = time.time()
    for i in range(4):
        # p = Process(target=work)  #2.416138172149658
        p = Thread(target=work) #2.0021145343780518
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop_time = time.time()
    print(stop_time-start_time)


#结果验证正确

  

#二、GIL与普通锁的对比

#不加其他锁

from threading import Thread
import time

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

t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()
print(n)  #99
#解释:当启动线程后,由于GIL的存在,一次只能运行一个线程,在其拿到n=100的值后,遇到IO,进入阻塞态,这时
#系统强制结束该线程,换其他线程来执行,如此循环,导致所有线程拿到的都是n=100的值,最后一个线程结束后,返回的值是
# n=100-1,则为99。 这个结果没有达到我们的初衷,因此需要依靠其他锁来保证数据的安全


#加上其他锁

from threading import Thread,Lock
import time

n=100
mutex = Lock()
def task():
    global n
    mutex.acquire()
    tmp = n
    time.sleep(0.1)
    n = tmp - 1
    mutex.release()

t_list = []
for i in range(100):
    t = Thread(target=task)
    t_list.append(t)
    t.start()
for t in t_list:
    t.join()
print(n) #0

#解释:加上了锁以后,第一个线程先拿到了锁,拿到了n=100这个数据,然后遇到了IO阻塞,系统强制结束该进程,其他线程开始运行,但是
#发现数据被加锁了,无法进一步运行,到时间了,系统又让其强制结束,如此往复,直到第一个进程再次拿到了执行权限,此时的阻塞已经过去,
# 该进程进行相关运算,再释放锁,交给其他线程去竞争,如此循环往复,最后得到n = 0



因此对于不同的数据,要想保证安全,需要加上不同的锁去处理
GIL并不能保证数据的安全,他是对Cpython解释器加锁,针对的是线程
保证的是在同一个进程中多个线程一个时间内只能运行一个线程

  

#三、死锁与递归锁

#死锁

from threading import Thread,Lock
import time

mutex1 = Lock()
mutex2 = Lock()

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

    def fun1(self):
        mutex1.acquire()
        print('%s 抢到A锁'%self.name)
        mutex2.acquire()
        print('%s 抢到了B锁'%self.name)
        mutex2.release()
        print('%s释放了B锁'%self.name)
        mutex1.release()
        print('%s 释放了A锁'%self.name)

    def fun2(self):
        mutex2.acquire()
        print('%s抢到了B锁'%self.name)
        time.sleep(1)
        mutex1.acquire()
        print('%s 抢到了A锁'%self.name)
        mutex1.release()
        print('%s 释放了A锁'%self.name)
        mutex2.release()
        print('%s 释放了B锁'%self.name)

for i in range(100):
    t = MyThead()
    t.start()

#结果:

Thread-1 抢到A锁
Thread-1 抢到了B锁
Thread-1释放了B锁
Thread-1 释放了A锁
Thread-1抢到了B锁
Thread-2 抢到A锁
然后程序就会卡着,此时就陷入了死锁 
因为线程1拿到了B锁,线程2拿到了A锁,此时彼此拿着对方的命脉,不给对方活路,除非有外力,不然就会一直这样


#递归锁

from threading import RLock,Thread
import time

mutexA = mutexB =RLock()

class MyThead(Thread):
    def run(self):
        self.fun1()
        self.fun2()
    def fun1(self):
        mutexA.acquire()
        print('%s抢到了A锁'%self.name)
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        mutexB.release()
        print('%s释放了B锁'%self.name)
        mutexA.release()
        print('%s释放了A锁'%self.name)
    def fun2(self):
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        time.sleep(1)
        mutexA.acquire()
        print('%s抢到了A锁'%self.name)
        mutexA.release()
        print('%s释放了A锁'%self.name)
        mutexB.release()
        print('%s释放了B锁'%self.name)

for i in range(10):
    t = MyThead()
    t.start()


#总结

自定义锁一次acquire必须对应一次release,不能连续acquire
递归锁Rlock:这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
直到一个线程所有的acquire都被release,其他的线程才能获得资源。
mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,
这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

  

#四、信号量

from threading import Thread,Semaphore
import time,random

sm = Semaphore(5)  #相当于厕所有5个茅坑

def task(name):
    sm.acquire()
    print('%s正在蹲坑'%name)
    time.sleep(random.randint(1,5)) #模拟蹲坑耗时
    sm.release()

for i in range(20):
    t = Thread(target=task,args=('巴豆%s号'%i,))
    t.start()
#和普通的互斥锁区别在于,普通的是独立卫生间,所有人抢一个坑位
#信号量 是公共卫生间,有多个茅坑,所有人抢多个坑位

  

#五、线程 queue   使用import queue ,用法与进程的Queue一样
#先进先出 queue.Queue
#先进后出 queue.LifoQueue
#优先级   queue.PriorityQueue


import queue

q = queue.Queue(3) #队列里最多放置的数据个数
q.put(1)
q.put(2)
print(q.get()) #1
print(q.get()) #2

q = queue.LifoQueue(3)
q.put(1)
q.put(2)
print(q.get()) #2
print(q.get()) #1

q = queue.PriorityQueue(3)
q.put((10,'a'))
q.put((-1,'a'))
q.put((0,'a'))
print(q.get()) #(-1, 'a')
print(q.get()) #(0, 'a')
print(q.get()) #(10, 'a')
#对于优先级,元组里的第一个元素通常是数字,也可以是非数字之间去比较大小
#比较的结果中,该元素越小,优先级越高

  

#六、event事件

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步
的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程
设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象
, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,
它将唤醒所有等待这个Event对象的线程。
如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

#多线程尝试连接MySQL
from threading import Thread,Event
import threading
import time,random

# event = Event()
def conn_mysql():
    count = 1
    while not event.is_set():
        if count > 3:
            raise TimeoutError('链接超时')
        print('<%s>第%s尝试链接'%(threading.current_thread().getName(),count))
        event.wait(0.5)
        count += 1
    print('<%s>链接成功'%threading.current_thread().getName())

def check_mysql():
    print('\033[45m[%s]正在检查mysql\033[0m'%threading.current_thread().getName())
    time.sleep(random.random())
    event.set()
event = Event()
conn1 = Thread(target=conn_mysql)
conn2 = Thread(target=conn_mysql)
check = Thread(target=check_mysql)

conn1.start()
conn2.start()
check.start()

  

猜你喜欢

转载自www.cnblogs.com/changwenjun-666/p/10833217.html
今日推荐