Day 36 GIL锁线程池信号量Event事件线程间通信

一、验证GIL锁的存在

​ 我们如何验证GIL锁呢?我们知道因为有GIL锁所以我们所谓的多线程实际上只是一个线程,即使我们开很多个线程,其实也只是利用到了CPU的一个核心,而进程就不一样了,一个进程拥有一个GIL所以,我们开几个进程实际就是利用几个CPU

from threading import Thread
from multiprocessing import Process


def run():
    while True:
        pass


if __name__ == '__main__':
    for i in range(100):
        r1 = Thread(target=run)
        # r2 = Process(target=run)
        r1.start()

一下是运行线程时CPU的状态,并没有跑满在这里插入图片描述
在这里插入图片描述

二、GIL锁和普通锁的区别

GIL锁并不能保证数据的安全,普通互斥锁来保证数据的安全

举个栗子

1 GIL锁是不能保证数据的安全,普通互斥锁来保证数据安全
from threading import Thread, Lock
import time

mutex = Lock()
money = 100


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


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=task)
        t.start()
    time.sleep(2)  # 防止还没执行完毕 数据还没修改
    print(money)
------------------------------------
99
     
解释GIL锁的运行原理,我们开了是个线程,当第一个线程拿到GIL锁时,temp=money=100,然后下一步进入io操作,第二个线程拿到GIL锁,也执行temp=money但是此时money并没有修改,所以仍然是100,诸如此类,直到最后一个,如果我们没有赋值操作那么可以得到正确的结果

三、死锁现象

死锁

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

def run(name):
    A.acquire()
    print(f"{name}拿到了A锁")
    B.acquire()
    print(f"{name}拿到了B锁")
    B.release()
    print(f"{name}释放了B锁")
    A.release()
    print(f"{name}释放了A锁")

def start(name):
    B.acquire()
    print(f"{name}也拿到了B锁")
    A.acquire()
    time.sleep(2)
    print(f"{name}也拿到了A锁")
    A.release()
    print(f"{name}也释放了A锁")
    B.release()
    print(f"{name}也释放了B锁")

if __name__ == '__main__':
    name_l = ["gg", "ss", "ff"]
    for i in name_l:
        r = Thread(target=run, args=(i,))
        s = Thread(target=start, args=(i,))
        s.start()
        r.start()
------------------------------------------
>>> gg也拿到了B锁 
>>> gg也拿到了A锁
>>> gg也释放了A锁
>>> gg也释放了B锁  #由于启动线程需要花费时间,所以s先启动先拿到需要的两把锁BA,此时两把锁在自己手上,能够正常释放
>>> ss也拿到了B锁  # 此时s再次启动抢到的第一把锁B
>>> gg拿到了A锁  # 与此同时r启动也抢到了自己需要的第一把锁A
这个时候就有趣了起来s还需要一把锁A r还需要一把锁B,但此时这两把锁都在对方手上并没有释放,所以都陷入等待时间

解决死锁

要解决死锁我们可以规范自己的代码,个人感觉使用上下文管理器原理可以避免,先拿先放,再改再锁,同时也可以导入RLock类来解决问题。也就是递归锁

递归锁:这个RLock内部维护着一个Lock和一个counter计数器变量,每acquire一次,内部计数器加1,每relaese一次,内部计数器减一只有计数器不为0,其他人都不获得这把锁

我们只需要将源代码

from threading import Thread, RLock

# A = Lock()
# B = Lock()
改为
A = B = RLock()  # 如果我们这里还是用两种不同的锁,就和死锁没有区别,因为我们根本没有用上解决问题的 计数功能

四、semaphore信号量

threading模块里的Semaphore类实现了信号量对象,可用于控制获取资源的线程数量。所具有的acquire()release()方法,可以用with语句的上下文管理器。当进入时,将调用acquire()方法,当退出时,将调用release()

Semaphore的value参数表示内部计数器的初始值,默认值为0。信号量内部有个计数器,它的计算方式:

  • 每当调用acquire()时内置计数器-1;调用release() 时内置计数器+1
  • 计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
  • 如果我们把semaphore控制为3,也就是说,同时有3个线程可以用这个锁,剩下的线程也之只能是阻塞等待。
import time
import random

sm = Semaphore(3)  # 数字表示可以同时有多少个线程操作


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


if __name__ == '__main__':
    for i in range(100):
        t = Thread(target=task, args=(i,))
        t.start()
        
--------------------------------
>>> 0 正在蹲坑
>>> 1 正在蹲坑
>>> 2 正在蹲坑
# 会立马执行前三个线程
>>> 3 正在蹲坑
>>> 4 正在蹲坑  #后面这两个就变为串行操作了 一个完成了再进行下一个 很像上厕所有没有

五、Event事件

​ 线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。对象包含一个可有线程设置的信号标志,它允许线程等待某些时间的发生

  • 在初始情况下,Event对象中的信号标志被设为False
  • 如果有线程等待一个Event对象,而这个Event对象的标志为False,那么这个线程会被一直阻塞直至该标志为True。
  • 如果一个线程将Event对象的信号标志设置为True,他将唤醒所有等待这个Event对象的线程。
  • 如果一个线程等待一个已经被设置为True的Event对象,那么它将忽略这个事件, 继续执行

基本方法

flag = threading.Event() 这个对象里面的值默认是 False

  1. flag.wait():查看 flag 里面的值,如果这个值是 false 的话,就在这里等待它变成 True ,然后再执行后面的代码,括号里面可以写参数,意思为 超时执行
  2. flag.set() :把 flag 里面的值改为 True
  3. event.isSet():返回event的状态值
  4. flag.clear() : 把 flag 里面的值改为 False
import time
from threading import Thread, Event


def Shoes_on_sale():
    print("Nike准备5秒钟后发售新的AJ配色!")
    time.sleep(5)
    print("AJ韭菜配色正在发售")
    event.set()


def Chives(i):
    print(f"等待被收割的韭菜{i}号!")
    event.wait()
    print(f"我是韭菜{i}号,我已准备好抢鞋子了")


if __name__ == '__main__':
    event = Event()
    s = Thread(target=Shoes_on_sale)
    s.start()
    for i in range(10):
        c = Thread(target=Chives, args=(i+1, ))
        c.start()
        
---------------------------------------------------
>>> Nike准备5秒钟后发售新的AJ配色!
>>> 等待被收割的韭菜1号!
>>> 等待被收割的韭菜2号!
>>> 等待被收割的韭菜3号!
....................
>>> AJ韭菜配色正在发售
>>> 我是韭菜8号,我已准备好抢鞋子了
>>> 我是韭菜4号,我已准备好抢鞋子了
...................

六、线程Queue

线程和进程中的队列是两种不同的类,就像之前的Lock锁一样。同样队列也是为了解决共享变量间会出现数据不安全问题,用线程Queue通信,不需要加锁,内部自带

注意:线程中有三种队列

  • Queue队列:基本队列FIFO原则,先进先出
  • PriorityQueu:优先级队列,谁小谁先出
  • LifoQueue:栈,LIFO原则,后进先出
# q=Queue(5)
# q.put("lqz")
# q.put("egon")
# q.put("铁蛋")
# q.put("钢弹")
# q.put("金蛋")
#
#
# # q.put("银蛋")
# # q.put_nowait("银蛋")
# # 取值
# print(q.get())
# print(q.get())
# print(q.get())
# print(q.get())
# print(q.get())
# # 卡住
# # print(q.get())
# # q.get_nowait()
# # 是否满,是否空
# print(q.full())
# print(q.empty())

# LifoQueue

# q=LifoQueue(5)
# q.put("lqz")
# q.put("egon")
# q.put("铁蛋")
# q.put("钢弹")
# q.put("金蛋")
# #
# # q.put("ddd蛋")
# print(q.get())


#PriorityQueue:数字越小,级别越高

# q=PriorityQueue(3)
# q.put((-10,'金蛋'))
# q.put((100,'银蛋'))
# q.put((101,'铁蛋'))
# # q.put((1010,'铁dd蛋'))  # 不能再放了
#
# print(q.get())
# print(q.get())
# print(q.get())

七、线程池

现成的创建是需要消耗系统资源的,我们创建很多线程往往浪费了很多资源,我们可以限制执行的任务数量,每个线程分配一个任务,剩下的任务排队等候,等某个线程完成任务后,排队任务就可以安排个这个线程继续执行。简单来说就是我们有很多任务,但是我们只让几个线程来完成这些任务,限制多少个线程就是利用线程池来完成。

threading.``current_thread():返回当前对应调用者的控制线程的 Thread 对象。如果调用者的控制线程不是利用 threading 创建,会返回一个功能受限的虚拟线程对象

import time
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor
import random

pool = ThreadPoolExecutor(max_workers=3)  # 设置线程池中的数量为三个


def print_data(i):
    time.sleep(random.randint(1, 3))
    print(f"我是线程{current_thread().getName()}我正在执行第{i}个任务")
    return print(f"第{i}个任务结束")


def call_back(f):
    pass


if __name__ == '__main__':
        # ll=[]
    # for i in range(10):  # 起了10个线程
    #     # t=Thread(target=print_data)
    #     # t.start()
    #     res = pool.submit(task, i)  # 不需要再写在args中了
    #     # res是Future对象  任务的句柄
    #     # from  concurrent.futures._base import Future
    #     # print(type(res))
    #     # print(res.result())  # 像join 获取任务的结果(返回值),这个方法是阻塞的
    #     ll.append(res)
    #
    # for res in ll:
    #     print(res.result())
    for i in range(10):
        pool.submit(print_data, i + 1).add_done_callback(call_back)
        
       # 将一个任务放入任务队列,并且start()一个线程,并让这个线程从队列取出和完成fn这个任务,args是fn任务函数的参数;返回的task是一个Future对象
    
---------------------------------
>>> 我是线程ThreadPoolExecutor-0_0我正在执行第1个任务
>>> 我是线程ThreadPoolExecutor-0_1我正在执行第2个任务
>>> 我是线程ThreadPoolExecutor-0_2我正在执行第3个任务
>>>2个任务结束
>>> 我是线程ThreadPoolExecutor-0_1我正在执行第4个任务
>>>3个任务结束

从上面的代码我们发现确实只有三个线程在跑,并且是跑完后再分配给原来的三个线程

基本用法:

ThreadPoolExecutor(_base.Executor)
   def __init__(self, max_workers=None, thread_name_prefix='',
                 initializer=None, initargs=()):
        
max_workers:可用于执行给定调用的最大线程数。 默认5个或者CPU核心数*5
thread_name_prefix:提供线程的可选名称前缀。初始化程序:用于初始化工作线程的可调用对象。 
initargs:传递给初始化程序的参数的元组。
submit(self, fn, *args, **kwargs):

使用 submit 函数来提交线程需要执行的任务到线程池中,并返回该任务的句柄(类似于文件、画图),注意 submit() 不是阻塞的,而是立即返回。

  • fn 需要执行的任务\函数
  • *args, **kwarg任务\函数的参数
class Future(object):
    # Future是一个存储着异步任务的执行结果和运行状态的容器;当然Future不只是一个容器,还负责一个任务的结果获取,取消,判断是否完成,异常设置等功能。一个Future对应着一个任务(任务函数)的结果和状态。

    def __init__(self):
        # Future对象不能在客户端脚本实例化,只能在ThreadPoolExecutor这样的python内部代码中实例化
        
        self._condition = threading.Condition()     # 创建了一个条件变量
        self._state = PENDING                       # 任务状态默认是“正在执行”
        self._result = None                         # 任务结果默认为空,因为任务还没开始执行或者正在执行
        self._exception = None              
        self._waiters = []                          
        self._done_callbacks = []                   

    def _invoke_callbacks(self):
        for callback in self._done_callbacks:
            try:
                callback(self)
            except Exception:
                LOGGER.exception('exception calling callback for %r', self)

    # 用于取消执行某一个任务
    def cancel(self):
        # 翻译: 不能取消一个正在执行或已执行完的任务。只能取消还未执行的任务。什么是未执行的任务?线程池ThreadPoolExecutor限定了能同时处于执行状态的线程数n,但往线程池中添加的任务数m如果超过了n,m个任务中只有n个可以同时执行,那么m-n那部分任务需要等待前面的任务执行完才能开始执行,这些就是还未执行的任务。
        
        with self._condition:
            if self._state in [RUNNING, FINISHED]:
                return False

            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
                return True

            self._state = CANCELLED
            self._condition.notify_all()    # 取消任务的时候,会唤醒 get_result()中条件变量的wait(),目的是为了告诉get_result说:“不必等待已取消的任务的结果了”

        self._invoke_callbacks()
        return True

    # 判断这个Future任务是否处于“已被取消”状态
    def cancelled(self):
        """Return True if the future was cancelled."""
        with self._condition:
            return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]

    # 判断这个Future任务是否处于“正在运行”状态
    def running(self):
        """Return True if the future is currently executing."""
        with self._condition:
            return self._state == RUNNING

    # 判断这个Future任务是否处于“执行完毕”状态
    def done(self):
        """Return True of the future was cancelled or finished executing."""
        with self._condition:
            return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]

    # 获取任务函数的返回值
    def __get_result(self):
        if self._exception:
            raise self._exception
        else:
            return self._result

    # 添加任务函数执行后要执行的函数
    def add_done_callback(self, fn):
        # 翻译:该方法用于添加一个函数,这个函数会在任务函数完成或者被取消的时候执行。函数会被存放到_done_callbacks这个列表中。所以可以为一个任务函数添加多个这样的函数。这些_done_callbacks中的函数的执行顺序是按照存入列表的顺序执行的。
        
        with self._condition:
            if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]:
                self._done_callbacks.append(fn)
                return
        fn(self)


    # 获取任务执行的结果,由于使用了_self.condition.wait()方法,所以是个阻塞的方法。
    def result(self, timeout=None):
        # 获取Future任务函数的返回值。由于要获取任务返回值,所以肯定要等任务执行完才能获取返回值,所以result()方法会阻塞等待Future对象中的任务函数执行完才能被唤醒。
        
        with self._condition:
            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
                raise CancelledError()      # 如果对已取消的任务获取返回值结果会报错
            elif self._state == FINISHED:   # 如果对已执行完毕的任务获取结果则无需等待,直接返回结果
                return self.__get_result()

            self._condition.wait(timeout)   # 如果对正在执行的任务或者还未执行的任务获取结果,则需要等待,等到任务结束了才能获取结果

            # 此时 wait() 被唤醒,但是仍要再判断一次任务的状态,因为这个任务可能是还未执行的任务被取消了,取消任务是也会notify()唤醒wait()
            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
                raise CancelledError()
            elif self._state == FINISHED:
                return self.__get_result()
            else:
                raise TimeoutError()

    # 用于获取任务执行时报的异常
    def exception(self, timeout=None):
        with self._condition:
            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
                raise CancelledError()
            elif self._state == FINISHED:
                return self._exception

            self._condition.wait(timeout)

            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
                raise CancelledError()
            elif self._state == FINISHED:
                return self._exception
            else:
                raise TimeoutError()

    # 设置任务结果
    def set_result(self, result):
        # 这个方法只能在任务执行完毕时在线程池的线程中调用,而不能在主线程中调用
        
        with self._condition:
            self._result = result       # 将任务结果存到对象属性中
            self._state = FINISHED      # 将任务状态改为结束
            for waiter in self._waiters:
                waiter.add_result(self)
            self._condition.notify_all()    # 唤醒和通知 result()中的wait(),说:“任务函数的结果已经拿到了,可以把结果返回给主线程了”
        self._invoke_callbacks()    # 执行任务函数完成后要执行的函数

    def set_exception(self, exception):
        with self._condition:
            self._exception = exception
            self._state = FINISHED
            for waiter in self._waiters:
                waiter.add_exception(self)
            self._condition.notify_all()
        self._invoke_callbacks()

进程池

# 1 如何使用
from concurrent.futures import ProcessPoolExecutor
pool = ProcessPoolExecutor(2)
pool.submit(get_pages, url).add_done_callback(call_back)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值