ITPub博客

首页 > 数据库 > 数据库开发技术 > python 多线程同步问题: Semaphore 信号量初步了解

python 多线程同步问题: Semaphore 信号量初步了解

原创 数据库开发技术 作者:程序员小钟同学 时间:2021-05-25 17:30:09 0 删除 编辑

PS:Lock、Condition、Semaphore都支持上下文协议,内部enter和exit方法都是使用的acquire和release方法 就是都支持:with 语法

Semaphore 信号量

主要是提供一个有限的线程池空间,给每个一个线程分配一个锁,机制,当线程调度执行完成,让出自身持有的锁,分给其他等待的线程。

PS:意思就是:在创建多线程的时候,限制同一时间最多运行N个线程。避免无线创建线程。

semaphore信号量内部存在一个计数器,用于记录当前线程的数量计数,

  • acquire()会消耗信号量,计数器会自动减一;
  • release()会释放信号量,计数器会自动加一;
  • 当计数器为零时,acquire()调用被阻塞,内部的条件变量会让这个线程进入wait状态,直到release()释放信号量为止。

Semaphore 信号内部源码

class Semaphore:
    """This class implements semaphore objects.

    Semaphores manage a counter representing the number of release() calls minus
    the number of acquire() calls, plus an initial value. The acquire() method
    blocks if necessary until it can return without making the counter
    negative. If not given, value defaults to 1.

    "
""

    # After Tim Peters' semaphore class, but not quite the same (no maximum)

    def __init__(self, value=1):
        if value < 0:
            raise ValueError("semaphore initial value must be >= 0")
        self._cond = Condition(Lock())
        self._value = value

    def acquire(self, blocking=True, timeout=None):
        """Acquire a semaphore, decrementing the internal counter by one.

        When invoked without arguments: if the internal counter is larger than
        zero on entry, decrement it by one and return immediately. If it is zero
        on entry, block, waiting until some other thread has called release() to
        make it larger than zero. This is done with proper interlocking so that
        if multiple acquire() calls are blocked, release() will wake exactly one
        of them up. The implementation may pick one at random, so the order in
        which blocked threads are awakened should not be relied on. There is no
        return value in this case.

        When invoked with blocking set to true, do the same thing as when called
        without arguments, and return true.

        When invoked with blocking set to false, do not block. If a call without
        an argument would block, return false immediately; otherwise, do the
        same thing as when called without arguments, and return true.

        When invoked with a timeout other than None, it will block for at
        most timeout seconds.  If acquire does not complete successfully in
        that interval, return false.  Return true otherwise.

        "
""
        if not blocking and timeout is not None:
            raise ValueError("can't specify timeout for non-blocking acquire")
        rc = False
        endtime = None
        with self._cond:
            while self._value == 0:
                if not blocking:
                    break
                if timeout is not None:
                    if endtime is None:
                        endtime = _time() + timeout
                    else:
                        timeout = endtime - _time()
                        if timeout <= 0:
                            break
                self._cond.wait(timeout)
            else:
                self._value -= 1
                rc = True
        return rc

    __enter__ = acquire

    def release(self):
        """Release a semaphore, incrementing the internal counter by one.

        When the counter is zero on entry and another thread is waiting for it
        to become larger than zero again, wake up that thread.

        "
""
        with self._cond:
            self._value += 1
            self._cond.notify()

    def __exit__(self, t, v, tb):
        self.release()

从源码中可以看到,信号量,使用我们的条件变量来进行相关的线程的通知和唤醒的机制。

Semaphore 和 BoundedSemaphore

相同的地方:

  • Semaphore类和BoundedSemaphore类都是信号量类
  • acquire()会消耗信号量,计数器会自动减一;
  • release()会释放信号量,计数器会自动加一;
  • 计数器为0的时候其它线程就被阻塞无法获得信号量

区别:

  • 当计数器为设定好的上限的时候BoundedSemaphore就无法进行release()操作了,Semaphore没有这个限制检查。

BoundedSemaphore 简单示例:

同一个时间内只运行运行三个线程:

from threading import Thread, BoundedSemaphore
from time import time, sleep
import threading

def execute(i):
    start = time()
    with sem:
        end = time()
        print("线程ID: {0}, 名字{1}等待事件:{2}".format(threading.get_ident(),threading.current_thread().getName(),end - start))
        sleep(2)

sem = BoundedSemaphore(3)  # 控制同一时刻最多允许三个线程访问资源
for i in range(12):
    Thread(target=execute,name="我是线程:%s"%(i),args=(i,)).start()

输出结果:

线程ID: 31384, 名字我是线程:0等待事件:0.0
线程ID: 9152, 名字我是线程:1等待事件:0.0
线程ID: 31220, 名字我是线程:2等待事件:0.0
线程ID: 26436, 名字我是线程:3等待事件:2.002983331680298
线程ID: 22184, 名字我是线程:5等待事件:2.002983331680298
线程ID: 32456, 名字我是线程:4等待事件:2.002983331680298
线程ID: 30844, 名字我是线程:6等待事件:4.0141541957855225
线程ID: 14732, 名字我是线程:7等待事件:4.013157606124878
线程ID: 22176, 名字我是线程:8等待事件:4.013157606124878
线程ID: 23556, 名字我是线程:9等待事件:6.024932622909546
线程ID: 30632, 名字我是线程:11等待事件:6.024932622909546
线程ID: 21276, 名字我是线程:10等待事件:6.024932622909546

Semaphore 和 BoundedSemaphore可以应用的场景:

  • 限制数据库的连接数(数据库连接池)
  • 控制并发数量(爬虫并发控制)

个人其他博客地址

简书:https://www.jianshu.com/u/d6960089b087

掘金:https://juejin.cn/user/2963939079225608

小钟同学 | 文 【原创】| QQ:308711822

  • 1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。
  • 2:关于文章内容,有部分内容参考自互联网整理,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。


原文作者:程序员小钟同学;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-02-20

  • 博文量
    224
  • 访问量
    111765