Semaphore

信号量,信号量对象内部维护一个倒计数器,每一次acquire都会减1,当acquire方法发现计数为0就阻塞请求的线程,直到其它线程对信号量release后,计数大于0,恢复阻塞的线程。

方法:

Semaphore(value=1)                            构造方法。value小于0,抛ValueError异常。默认为1。

acquire(blocking=True,timeout=None)  获取信号量,计数器减1,获取成功返回True。

release()                                               释放信号量,计数器加1。

计数器永远不会低于0,因为acquire的时候,发现是0,都会被阻塞。

举例:

图书馆有三本书,三本都被借走(acquire)之后,其他人想看,就得等别人还回来(阻塞),有人还回来(release)一本后,就有一个人可以拿到这本书,其他人仍然得等归还。

#Semaphore 信号量,借还
import threading,logging,time
DATEFMT="%H:%M:%S"
FORMAT = "[%(asctime)s]\t [%(threadName)s,%(thread)d] %(message)s"
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT) def work(s:threading.Semaphore):
logging.info('in sub thread')
logging.info(s.acquire())
logging.info('sub thread oevr') s = threading.Semaphore(3)
logging.info(s.acquire())
logging.info(s.acquire())
logging.info(s.acquire()) threading.Thread(target=work,args=(s,)).start()
time.sleep(2) logging.info(s.acquire(False)) #不阻塞
logging.info((s.acquire(timeout=3))) #3秒超时会阻塞 logging.info('release')
s.release() 运行结果:
[08:48:43] [MainThread,8840] True
[08:48:43] [MainThread,8840] True
[08:48:43] [MainThread,8840] True
[08:48:43] [Thread-1,6212] in sub thread
[08:48:45] [MainThread,8840] False
[08:48:48] [MainThread,8840] False
[08:48:48] [MainThread,8840] release
[08:48:48] [Thread-1,6212] True
[08:48:48] [Thread-1,6212] sub thread oevr

  这个例子只起了一个线程,如果多起几个,当release还回来的数小于阻塞的线程数时,程序就会一直处于阻塞状态,直到全部relase。

应用举例:

因为资源有限,且开启一个连接成本高,所以,使用连接池。

一个简单的连接池(例子):

连接池应该有容量(value总数),也应该工厂方法可以获取连接,能够把不用的连接归还,供其他使用者使用。

#一个简单的连接池
import threading,logging,time
DATEFMT="%H:%M:%S"
FORMAT = "[%(asctime)s]\t [%(threadName)s,%(thread)d] %(message)s"
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT) class Conn:
def __init__(self,name):
self.name = name class Pool:
def __init__(self,count=3):
self.count = count
#连接池容器
self.pool = [self._connect('conn-{}'.format(x)) for x in range(self.count)] def _connect(self,conn_name):
return Conn(conn_name) def get_conn(self):
# if len(self.pool) > 0:
return self.pool.pop() #从尾部弹出一个 def return_conn(self,conn:Conn):
self.pool.append(conn) pool = Pool(3)
print(pool.pool)
pool.get_conn()
pool.get_conn()
pool.get_conn()
pool.get_conn() #第4个 print('End Main') 运行结果:
[<__main__.Conn object at 0x00000211BBEBC160>, <__main__.Conn object at 0x00000211BBEBC1D0>, <__main__.Conn object at 0x00000211BBEBC240>]
Traceback (most recent call last):
File "C:/python/test.py", line 34, in <module>
pool.get_conn()
File "C:/python/test.py", line 24, in get_conn
return self.pool.pop() #从尾部弹出一个
IndexError: pop from empty list

  当连接池中已经没有可用连接时,再获取就会抛异常 IndexError:pop from empty list。

那就加个判断,只在池中连接数量大于0的时候才可以获取连接:

#修改get_conn函数
def get_conn(self):
if len(self.pool) > 0:
return self.pool.pop() #从尾部弹出一个

  这样在连接池为空时,就不会抛异常了。

这个连接池的例子如果使用多线程,这个get_conn()方法是线程不安全的,有可能其它线程看到池中还有一个连接,正准备获取,其它线程也看到了,也准备获取连接,就会抛异常。再或者,都在向池中加连接的时候,也可能会多加。

这个问题可以用锁Lock来解决, 在获取连接和加连接时,加锁解锁;也可以使用semaphore信号量来解决。

使用信号量对上例进行修改:

#使用semaphore信号量修改连接池
import threading,logging,time,random
DATEFMT="%H:%M:%S"
FORMAT = "[%(asctime)s]\t [%(threadName)s,%(thread)d] %(message)s"
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT) class Conn:
def __init__(self,name):
self.name = name def __repr__(self):
return self.name class Pool:
def __init__(self,count=3):
self.count = count
#连接池容器
self.pool = [self._connect('conn-{}'.format(x)) for x in range(self.count)]
self.semaphore = threading.Semaphore(self.count) def _connect(self,conn_name):
#返回一个连接名
return Conn(conn_name) def get_conn(self):
#从池中拿走一个连接
# if len(self.pool) > 0:
self.semaphore.acquire(timeout=5) #-1,获取连接,最大5秒超时时间,与后面随机秒数相对应
data = self.pool.pop() #从尾部弹出一个
return data def return_conn(self,conn:Conn):
#向池中添加一个连接
self.pool.append(conn)
self.semaphore.release() # 先加入池中再信号量+1
return len(self.pool) pool = Pool(3) def worker(pool:Pool):
conn = pool.get_conn()
logging.info(conn)
#模拟使用了资源一段时间(随机1-4秒),然后归还
threading.Event().wait(timeout=random.randint(1,4))
pool.return_conn(conn) for i in range(6):
threading.Thread(target=worker,name="worker-{}".format(i),args=(pool,)).start() print('End Main') 运行结果:
[10:34:12] [worker-0,5264] conn-2
[10:34:12] [worker-1,7420] conn-1
[10:34:12] [worker-2,2612] conn-0
End Main
[10:34:13] [worker-3,3972] conn-1 #归还以后又可以获取连接
[10:34:14] [worker-4,8172] conn-2
[10:34:15] [worker-5,11192] conn-1

  上例中模拟获取连接以后使用了1-4秒钟,没有拿到资源的最多阻塞5秒钟,当连接使用结束归还后,阻塞的线程就又重新获取到连接。

问题:

1) 没有使用信号量就release的情况:

import threading

s = threading.Semaphore(3)
print(s.__dict__) def work(s:threading.Semaphore):
s.release() for i in range(3):
threading.Thread(target=work,args=(s,)).start()
print(s.__dict__) 运行结果:
{'_cond': <Condition(<unlocked _thread.lock object at 0x00000219202973A0>, 0)>, '_value': 3}
{'_cond': <Condition(<unlocked _thread.lock object at 0x00000219202973A0>, 0)>, '_value': 2}
{'_cond': <Condition(<unlocked _thread.lock object at 0x00000219202973A0>, 0)>, '_value': 3}
{'_cond': <Condition(<unlocked _thread.lock object at 0x00000219202973A0>, 0)>, '_value': 4}
{'_cond': <Condition(<unlocked _thread.lock object at 0x00000219202973A0>, 0)>, '_value': 5}

  没有acquire信号量时,就release的情况,结果导致了信号量的内置倒计数器的值增加,这样就超出了最大值。

解决办法:

使用BoundedSemaphore类:

BoundedSemaphore,继承自Semaphore类。边界绑定,有界的信号量,不允许使用release超过初始值的范围,否则,抛ValueError异常。

#BoundedSemaphore边界绑定
import threading s = threading.BoundedSemaphore(3)
print(s.__dict__) s.acquire()
print(s.__dict__) def work(s:threading.BoundedSemaphore):
s.release() for i in range(3):
threading.Thread(target=work,args=(s,)).start()
print(s.__dict__) 运行结果:
{'_value': 3, '_cond': <Condition(<unlocked _thread.lock object at 0x000001A42DDF73A0>, 0)>, '_initial_value': 3}
{'_value': 2, '_cond': <Condition(<unlocked _thread.lock object at 0x000001A42DDF73A0>, 0)>, '_initial_value': 3}
{'_value': 3, '_cond': <Condition(<unlocked _thread.lock object at 0x000001A42DDF73A0>, 0)>, '_initial_value': 3}
{'_value': 3, '_cond': <Condition(<unlocked _thread.lock object at 0x000001A42DDF73A0>, 0)>, '_initial_value': 3}
{'_value': 3, '_cond': <Condition(<unlocked _thread.lock object at 0x000001A42DDF73A0>, 0)>, '_initial_value': 3}
Exception in thread Thread-2:
Traceback (most recent call last):
File "C:/python/test.py", line 11, in work
s.release()
ValueError: Semaphore released too many times

  使用BoundedSemaphore就可以控制不会多归还。

[Python 多线程] Semaphore、BounedeSemaphore (十二)的更多相关文章

  1. Python 多线程、多进程 (二)之 多线程、同步、通信

    Python 多线程.多进程 (一)之 源码执行流程.GIL Python 多线程.多进程 (二)之 多线程.同步.通信 Python 多线程.多进程 (三)之 线程进程对比.多线程 一.python ...

  2. Python开发【第二十二篇】:Web框架之Django【进阶】

    Python开发[第二十二篇]:Web框架之Django[进阶]   猛击这里:http://www.cnblogs.com/wupeiqi/articles/5246483.html 博客园 首页 ...

  3. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  4. “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  5. python自动华 (十二)

    Python自动化 [第十二篇]:Python进阶-MySQL和ORM 本节内容 数据库介绍 mysql 数据库安装使用 mysql管理 mysql 数据类型 常用mysql命令 创建数据库 外键 增 ...

  6. Java 多线程基础(十二)生产者与消费者

    Java 多线程基础(十二)生产者与消费者 一.生产者与消费者模型 生产者与消费者问题是个非常典型的多线程问题,涉及到的对象包括“生产者”.“消费者”.“仓库”和“产品”.他们之间的关系如下: ①.生 ...

  7. python运维开发(十二)----rabbitMQ、pymysql、SQLAlchemy

    内容目录: rabbitMQ python操作mysql,pymysql模块 Python ORM框架,SQLAchemy模块 Paramiko 其他with上下文切换 rabbitMQ Rabbit ...

  8. Python学习【第十二篇】模块(2)

    序列化 1.什么是python序列化? 把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling 序列化就是将python的数据类型转换成字符串 反序列化就是将字符串转换成 ...

  9. Python 多线程进程高级指南(二)

    本文是如何<优雅地实现Python通用多线程/进程并行模块>的后续.因为我发现,自认为懂了一点多线程开发的皮毛,写了那么个multi_helper的玩意儿,后来才发现我靠原来就是一坨屎.自 ...

随机推荐

  1. [linux] C语言Linux系统编程进程基本概念

    1.如果说文件是unix系统最重要的抽象概念,那么进程仅次于文件.进程是执行中的目标代码:活动的.生存的.运行的程序. 除了目标代码进程还包含数据.资源.状态以及虚拟化的计算机. 2.进程体系: 每一 ...

  2. MySQL的预编译功能

      1.预编译的好处 大家平时都使用过JDBC中的PreparedStatement接口,它有预编译功能.什么是预编译功能呢?它有什么好处呢? 当客户发送一条SQL语句给服务器后,服务器总是需要校验S ...

  3. 有关动态规划(主要是数位DP)的一点讨论

    动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法.20世纪50年代初美国数学家在研究多阶段决策过程的优化问题时, ...

  4. Git与Github。

    Git是一款免费,开源的分布是版本,用于敏捷高效的处理任何或小或大的项目.分布式相对于集中式的最大区别在于开发者可以提到本地,每个开发者通过克隆,在本地磁盘内拷贝一个完整的GIt仓库. Git的功能特 ...

  5. EF学习之CodeFirst(二)--数据迁移

    使用CodeFirst时,如果Model发生改变的话,例如我们给User类里面新加个Sex属性,运行时会出现如下错误: 这时我们需要使用数据迁移来将model的改变同步更新到数据库中. 1.启用数据迁 ...

  6. 火狐浏览器对svg支持的一点不足

    项目中要用svg实现一个如下图所示的风机扇叶转动效果 当用chrome浏览器打开,动画显示正常.用火狐浏览器打开扇叶静止不动,代码如下: <svg xmlns:cge="http:// ...

  7. 内置的HTTP服务器【Modern PHP】

    目录 启动服务器 配置服务器 路由器脚本 判断是否为内置的服务器 PHP5.4.0起,PHP内置了Web服务器.对本地开发是个极好的工具,便捷,无需安装WAMP.XAMP或大新那个web服务器,就能在 ...

  8. C# 方法与参数 常见命名空间汇总 using的使用 main方法参数

    本文主要讲 C# 常见命名空间 using static 指令 && 调用静态方法 嵌套命名空间&&作用域 别名 Main() 方法 C# 常见命名空间 命名空间 作用 ...

  9. 沉淀,再出发:python爬虫的再次思考

    沉淀,再出发:python爬虫的再次思考 一.前言    之前笔者就写过python爬虫的相关文档,不过当时因为知识所限,理解和掌握的东西都非常的少,并且使用更多的是python2.x的版本的功能,现 ...

  10. August 01st 2017 Week 31st Tuesday

    A contented mind is the greatest blessing a man can enjoy in this world. 知足是人生在世最大的幸事. Being content ...