网络编程十:线程间的同步之信号量

总结:

- Lock:一个Lock对象有两个方法acquire和release来控制共享数据的读写权限。

- Event:一个进程发事件的信号,另一个进程等待事件的信号。Event对象有两个方法set和clear来管理自己内部的变量。

- Condition:此对象用来同步部分工作流程,在并行的进程中,有两个基本的方法,wait()用来等待进程,notify_all用来通知所有等待此条件的进程。

- Semaphore:用来共享资源,比如:支持固定数据的共享连接。

- RLock:递归锁对象,其用途和方法同Threading模块一样。

- Barrier:将程序分成几个阶段,适用于有些进程必须在某些特性进程之后执行,处于Barrier之后的代码不能同处于Barrier之前的代码并行。

信号量:threading.Semaphore
import threading
s = threading.Semaphore(3)  # 设置信号量:假设信号量为3,那么可加锁3次,3次后将阻塞
s.acquire()  # 返回True
s.acquire()  # 返回True
s.acquire()  # 返回True
s.acquire()  # 当加锁的次数超过信号量后,将阻塞,直到有线程释放锁

 

在加锁的时侯,如果指定blocking参数为False,则当加锁的次数超过信号量后,不阻塞,返回False。
blocking默认为True
s = threading.Semaphore(3)  # 设置信号量:假设信号量为3,那么可加锁3次,3次后将阻塞
s.acquire(False)  # 返回True
s.acquire(False)  # 返回True
s.acquire(False)  # 返回True
s.acquire(False)  # 当加锁的次数超过信号量后,不阻塞,但返回False

 

锁是信号量的特例,是信号量为1的特例。

信号量,不是rlock。信号量是可以在不同线程之间同步,rlock是只能在同一线程多次加锁。

示例:实现一个并发线程情形下的简单连接池。
注意:
  • 当连接池只有剩下一个连接时,多个连接请求的问题
  • 多个线程同时请求连接的争夺资源时侯,需要线程间的同步

可以用锁解决以上问题,但在这种情形下,使用信号量更好。

获取连接的时侯,加锁;释放连接的时侯,释放锁。

class ConnectionPool:
    def __init__(self, num):
        self.num = num
        # 初始化连接池
        self.cons = [self._mak_connect(x) for x in range(num)]
        # 信号量
        self.s = threading.Semaphore(num)
        
    def _make_connect(self, name):  # 创建连接
        return name
    
    def get(self):
        self.s.acquire()
        return self.conns.pop()
    
    def return_resource(self, con):
        self.conns.insert(con)
        self.s.release()

 

测试用例:创建最大值为3的连接池,创建5个线程从连接池中争夺连接。

import time
import threading
import random
import logging
import importlib
importlib.reload(logging)

logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s [%(threadName)s %(message)s]")

def worker(pool):
    logging.info("started...")
    name = pool.get()
    logging.info("got a connection {}".format(name))
    time.sleep(random.randint(1, 5))  # doing something
    pool.return_resource(name)
    logging.info("return a connection {}".format(name))
pool = ConnectionPool(3)
for x in range(5):
    threading.Thread(target=worker, args=(pool,), name="worker-{}".format(x)).start()

 

结果:worker-0,1,2争夺到连接,在释放连接之前,worker-3,4等待;当worker-1连接被释放后,worker3获取到连接,worker-0释放连接后,worker4获取到连接。

2018-12-11 22:28:21,188 INFO [worker-0 started...]
2018-12-11 22:28:21,237 INFO [worker-0 got a connection 2]
2018-12-11 22:28:21,234 INFO [worker-1 started...]
2018-12-11 22:28:21,271 INFO [worker-1 got a connection 1]
2018-12-11 22:28:21,305 INFO [worker-2 started...]
2018-12-11 22:28:21,331 INFO [worker-2 got a connection 0]
2018-12-11 22:28:21,339 INFO [worker-3 started...]
2018-12-11 22:28:21,394 INFO [worker-4 started...]
2018-12-11 22:28:22,272 INFO [worker-1 return a connection 1]
2018-12-11 22:28:22,272 INFO [worker-3 got a connection 1]
2018-12-11 22:28:23,256 INFO [worker-0 return a connection 2]
2018-12-11 22:28:23,256 INFO [worker-4 got a connection 2]
2018-12-11 22:28:24,334 INFO [worker-2 return a connection 0]
2018-12-11 22:28:26,278 INFO [worker-3 return a connection 1]
2018-12-11 22:28:28,286 INFO [worker-4 return a connection 2]

 

信号量也是对资源的保护,但信号量和锁不一样的地方在于:

锁限制只有一个线程可以访问共享资源,而信号量限制指定个线程可以访问共享资源

可见,信号量适用于像连接池一类的“池”有关的场景。

 





posted on 2018-11-04 18:01  myworldworld  阅读(148)  评论(0)    收藏  举报

导航