线程二

一:GIL(全局解释器锁)#

(1)官方解释:

复制代码
'''
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
'''
官方对CIL解释
复制代码

PS:

(1)python解释器有很多 但是常见的就是Cpython解释器

(2)GIL本质也是一把锁------->将程序运行由并发变成串行 提高数据的安全性 牺牲了执行效率

(3)GIL的存在是因为Cpython解释器的内存管理不是线程安全的

PS:图解上述多线程不安全的原因

(1)首先python代码想要执行 需要被解释器翻译成二进制 传给计算机

(2)假设没有GIL锁的情况下 即所有的线程都可以同时使用解释器进行翻译

(3)在所有线程都可以使用解释器的情况下 

(4)假设我此时定义一个变量 即将调用该变量

(5)但是此时垃圾回收机制触发 将还没有被调用的变量给清除 那么我变量便不能被调用了

(6)因此需要使用GIL锁对解释器进行加锁处理

PS:

(1)CIL不是python的特点 是Cpython的特点

(2)单进程下无法使用多核优势是解释型语言的通病

 

验证GIL与普通的互斥锁

例如:

复制代码
from threading import Thread
import time

n = 100

def run():
    global n
    temp = n
    # 模拟网络延迟
    time.sleep(1)
    n = temp - 1


t_list = []
for i in range(100):
    t = Thread(target=run)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()

print(n) # 99
GIL全局解释锁验证
复制代码

PS:

(1)首先上述结果不是为0 而是为99

(2)因为在上述中CPU沉睡1S 导致CPU释放 然后所有的线程都卡在sleep中

(3)在上述sleep之前所有的线程都拿到变量100 当执行完毕之后都进行减1

总结:GIL在多线程的情况下 不会进行并行 只会进行并发

 

(2)Python多核情况下多线程是否有用

(1)计算密集型

  单核情况:

   (1)开多线程:

    PS:在计算型情况下CPU在进行大量的运算 开多进程占用大量的资源 开多线程节省资源

  多核情况:

        (1)开多进程:

    PS:在多核情况下 如果处于多线程情况下CPU处于来回切换状态 效率低 而多进程 CPU可以处理多个进程下的多个线程

(2)I/O密集型

  (1)单核情况下

    (1)开多线程:

    PS:在I/O密集型情况下 程序会处于堵塞状态 开多进程浪费资源

  (2)多核情况下

    (1)开多线程

    PS:在I/O密集型情况下 程序会处于堵塞状态 开多进程浪费资源

代码示例:

复制代码
#  计算密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i


if __name__ == '__main__':
    l=[]
    print(os.cpu_count())  # 本机为4核
    start=time.time()
    for i in range(6):
        # p=Process(target=work) #耗时  4.732933044433594
        p=Thread(target=work) #耗时 22.83087730407715
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))
计算密集型
复制代码
复制代码
 # IO密集型
    from multiprocessing import Process
    from threading import Thread
    import threading
    import os, time


    def work():
        time.sleep(2)


    if __name__ == '__main__':
        l = []
        print(os.cpu_count())  # 本机为6核
        start = time.time()
        for i in range(4000):
            p = Process(target=work)  # 耗时9.001083612442017s多,大部分时间耗费在创建进程上
            # p=Thread(target=work) #耗时2.051966667175293s多
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop = time.time()
        print('run time is %s' % (stop - start))
I/O密集型
复制代码

PS:python多核多线程是否无用 需要看情况

  

二:死锁 + 递归锁#

死锁:两个或两个以上的线程/进程 两者相互持有对方所需要的锁且该锁不能被释放 造成互相等待的情况

例如:

复制代码
from threading import Thread,Lock
import time
mutexA = Lock()
mutexB = Lock()


class MyThread(Thread):
    def run(self):  # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().name
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        mutexB.release()
        print('%s释放了B锁'%self.name)
        mutexA.release()
        print('%s释放了A锁'%self.name)

    def func2(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 = MyThread()
死锁现象
复制代码

PS:

(1)假如线程A和B

(2)首先线程A执行func1此时抢到A和B两个锁 并且释放掉

(3)当A释放A和B两锁的时候 B抢到A锁并且要抢B锁

(4)当A释放完A和B两锁的时候 开始执行func2 此时A抢到了B锁 沉睡1S需要抢A锁

(5)此时A手里拿着B锁同时需要A锁 B手里拿着A锁同时需要B锁 就造成所需要的资源在对方手里 且两者都不能主动释放 造成死锁现象

 

递归锁:

作用:解决上述死锁的问题

解决的办法:

 (1)当有线程抢到该锁的时候

 (2)每acquire一次锁的计数+1

 (3)每release一次锁的计数-1

例如:

复制代码
import time
from threading import RLock,Thread

mutexA = mutexB = RLock()


class MyThread(Thread):
    def run(self):  # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().name
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        mutexB.release()
        print('%s释放了B锁'%self.name)
        mutexA.release()
        print('%s释放了A锁'%self.name)

    def func2(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 = MyThread()
    t.start()
递归锁
复制代码

PS:

(1)在上述中假设线程A在函数1中先拿到锁 经历两次计数两次释放此时计数为空

PS:不需要考虑当线程A释放锁的时候别的线程能拿到该锁底层帮我们处理这个问题了

(2)接着开始走函数2此时拿到锁B计数为1 接着拿到锁A计数为2 释放两次其余线程拿到锁

三:信号量#

作用:可以允许多个线程/进程使用锁

   PS:互斥锁(上厕所一个坑位)

    信号量(上厕所多个坑位)

例如:

复制代码
from threading import Semaphore,Thread
import time
import random


sm = Semaphore(5)  # 造了一个含有五个的坑位的公共厕所

def task(name):
    sm.acquire()
    print('%s占了一个坑位'%name)
    time.sleep(random.randint(1,3))
    sm.release()

for i in range(40):
    t = Thread(target=task,args=(i,))
    t.start()
信号量
复制代码

 

四:Event事件#

作用:子线程需要等待某个子线程结束之后才能开始执行

例如:

复制代码
from threading import Event,Thread
import time

# 先生成一个event对象
e = Event()


def light():
    print('红灯正亮着')
    time.sleep(3)
    e.set()  # 发信号
    print('绿灯亮了')

def car(name):
    print('%s正在等红灯'%name)
    e.wait()  # 等待信号
    print('%s加油门飙车了'%name)

t = Thread(target=light)
t.start()

for i in range(10):
    t = Thread(target=car,args=('伞兵%s'%i,))
    t.start()
信号量
复制代码

 

五:线程队列#

作用:

(1)队列是由管道 + 锁组成的 自带锁不需要我们手工操作锁

(2)防止造成死锁现象

例如:

import queue

q = queue.Queue()
q.put('hahha')
print(q.get())

 

后进先出队列:

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

 

优先级队列:

作用:

(1)优先级比较高 取出来的概率比较大

(2)优先级数值越小 优先级越高

(3)存放数据以元组形式存放 第一个参数是优先级 第二个参数是传的数据

q = queue.PriorityQueue()
# 数字越小 优先级越高
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get())

 

posted @   SR丶  阅读(129)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示
CONTENTS