day30

GIL全局解释器

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生不同的python进程

基于Cpython来研究全局解释锁

​ 1.GIL本质上是一个互斥锁

​ 2.GIL是为了阻止同一个进程内多个线程同时执行(并行)

​ 单个进程下的多个线程无法实现并行,但能实现并发

​ 3.这把锁主要是因为CPython的内存管理不是“线程安全”的

​ 内存管理

​ 垃圾回收机制

​ GIL的存放就是为了保证线程安全的

​ 注意:多线程过来执行,一旦遇到IO操作,就会立马释放GIL解释器,交给下一个先进来的进程

import time
from threading import Thread,current_thread
number = 100
def task():
    global number
    number2 = number
    number = number2 - 1
    print(number,current_thread().name)
for line in range(100):
    t = Thread(target=task)
    t.start()

验证多线程的作用

多线程的作用:

​ 站在两个角度去看问题:

​ 四个任务,计算密集型,每个任务需要10s

​ 单核:

​ 开启进程

​ 4个进程:40s

​ 开启线程

​ 消耗资源远小于进程

​ 4个线程:40s

​ 多核:

​ 开启进程

​ 并行执行,效率比较高

​ 4个进程:10s

​ 开启线程

​ 并发执行,执行效率低

​ 4个线程:40s

​ 四个任务,IO密集型,每个任务需要10s

​ 单核:

​ 开启进程

​ 消耗资源过大

​ 4个进程:40s

​ 开启线程

​ 消耗资源远小于进程

​ 4个线程:40s

​ 多核:

​ 开启进程
​ 并行执行,效率小于多线程,因为遇到IO立马切换CPU的执行权限

​ 4个进程:40s+开启进程消耗的额外时间

​ 4个线程:40s

在计算密集的情况下:

​ 使用多进程

在IO密集型的情况下:

​ 使用多线程

高效执行多进程内多个IO密集型的程序:

​ 使用 多进程+多线程

应用:

​ 多线程用于IO密集型,如socket,爬虫,web

​ 多进程用于计算密度型,如金融分析

死锁现象

​ 所谓死锁:指的是两个或两个以上的进程或线程在执行过程中,因为夺资源而造成的一种互相等待的现象,若无外力作用,它们都无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永久在互相等待的进程称为死锁进程。

from threading import Lock as Lock
import time
mutexA  = Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

解决死锁的方法:递归,在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护这一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果用RLock,则不会发生死锁。

典型问题:科学家吃面

死锁问题

#死锁的问题
import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒','nick','tank']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
递归锁解决死锁问题
import time
from threading import Thread,RLock
fork_lock = noodle_lock = RLock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒','nick','tank']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()

递归锁

递归锁(了解)
用于解决死锁问题
RLock:比喻成万能钥匙,可以提供给多个人去使用
但是第一个使用的时候,会对该锁做一个引用计数
只有引用计数为0时,才能真正释放让另一个去使用

from threading import RLock,Thread,Lock
import time
mutex_a  = mutex_b  = Lock()
class MyThread(Thread):
    #线程执行任务
    def run(self):
        self.func1()
        self.func2()
    def func1(self):
        mutex_a.acquire()
        print(f'用户{self.name}抢到了锁a')
        mutex_b.acquire()
        print(f'用户{self.name}抢到了锁b')
        mutex_b.release()
        print(f'用户{self.name}释放锁b')
        mutex_a.release()
        print(f'用户{self.name}释放锁a')
    def func2(self):
        mutex_b.acquire()
        print(f'用户{self.name}抢到锁b')
        #IO操作
        time.sleep(1)
        mutex_a.acquire()
        print(f'用户{self.name}抢到锁a')
        mutex_a.release()
        print(f'用户{self.name}释放锁a')
        mutex_b.release()
        print(f'用户{self.name}释放锁b')
for line in range(10):
    t= MyThread()
    t.start()

信号量

信号量(了解):
互斥锁:比喻成一个家用马桶

​ 同一时间只能让一个人去使用

信号量:比喻成公厕多个马桶,

​ 同一时间可以让多个人去使用

from threading import Semaphore,Lock
from threading import current_thread
from threading import Thread
import time
sm = Semaphore(5)
mutex = Lock()

def task():
    sm.acquire()
    print(f'{current_thread().name}执行任务')
    time.sleep(1)
    sm.release()
for line in range(20):
    t= Thread(target=task)
    t.start()

线程队列

线程Q(了解级别1):线程队列 面试会问:FIFO

​ FIFO队列:先进先出

​ LIFO队列:后进先出

​ 优先级队列:根据参数内,数字的大小进行分级,数字值越小,优先级越高

import queue
普通的线程队列:先进先出
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())  #1

LIFO队列:后进先出
q = queue.LiFoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())#3

优先级队列
q=queue.PriorityQueue()
#若参数中传的是元组,会以元组中第一个数字参数为准
q.put(('a优','娃娃头',4))#a==97  ascll码值
q.put(('b优','娃娃头',4))#b==98

1.首先根据第一个参数判断ascll表的数值大小
2.判断第几个参数中的汉字顺序
3.在判断第二个参数中的数字————》字符串数字---》中文
4.以此类推

current_thread:
返回当前线程对象,对应于调用方的控制线程。

如果调用者的控制线程不是通过线程创建的
模块,返回一个功能有限的虚拟线程对象。

原文地址:https://www.cnblogs.com/gfhh/p/11728104.html