Python多线程--(3)同步

有了python线程的基础概念Python多线程–(1)之基本概念
以及怎么在python中应用线程,threading模块的了解Python多线程–(2)之threading模块
然而多线程编程中还有最重要的一方面——同步。


同步原语

在编程中,有一些函数或者说是代码不应该多线程同时执行的,比如修改数据库、更新文件等会产生竞态条件的情况。可以想象一下,如果两个线程运行的顺序有变化,可能代码的运行轨迹也会发生变化,产生不同的数据结果。

这个时候就需要同步了,也就是多个线程都可以访问临界区的时候,必须保证任意时刻只会有一个线程在访问。这个时候就需要程序员选用适合的同步原语或者说线程控制机制来执行同步。python有多种同步类型,可以从最简单最低级的锁开始,还有用于多个线程竞争有限资源的信号量方式。


锁,可以直接理解为开门的那种。锁有两种状态,锁定和未锁定,它只支持两种函数,获得锁和释放锁。在多线程竞争锁的时候,第一个获得锁的线程进入临界区执行代码。其他的线程就被阻塞,直到第一个线程执行结束,退出临界区释放锁。这个时候再由其他线程竞争获得锁进入临界区。获得锁是没有次序的,都是随机分配的。

这个过程可以理解为一群人都想进入屋子,锁只有一把,有一个人拿到了锁,就把门锁起来了,其他人就进不去了,在门口等着他不想在屋子里了,把门打开锁交给下一个人。他们都不认识,所以是随机把锁给的下一个人了,然后得到锁的人又可以进去了,其他人就只能等着。

举个栗子

我们用下面这个例子来试用一下锁,如果不用锁,多线程得到的输出有可能就是混乱的,不过也不一定,可能它也是正常的,不过这个没法保证。

from threading import Thread, Lock, current_thread
from random import randrange
from atexit import register
from time import sleep, ctime


class CleanOutputSet(set):
    # 将输出列表换成以逗号分开的形式
    def __str__(self):
        return ','.join(x for x in self)


lock = Lock()  # 获取锁对象
loops = (randrange(2, 5) for x in range(randrange(3, 7)))  # 随机产生线程个数和等待时间
remaining = CleanOutputSet()  # 阻塞的线程


def run(nsec):
    my_name = current_thread().name  # 获取当前运行的线程名
    with lock:  # 支持上下文管理,获取锁
        remaining.add(my_name)
        print('[%s] Started %s' % (ctime(), my_name))
    sleep(nsec)
    with lock:  # 释放锁
        remaining.remove(my_name)
        print('[%s] Completed %s (%d secs)' % (ctime(), my_name, nsec))
        print('(remaining:%s)' % (remaining or None))


def main():
    for pause in loops:  # 运行加了锁的线程
        Thread(target=run, args=(pause,)).start()


@register
def _atexit():  # 等待退出时输出all done时间
    print('all done at:', ctime())


if __name__ == '__main__':
    main()

结果示例:(每次执行结果不唯一)

[Fri Aug 25 14:23:32 2017] Started Thread-1
[Fri Aug 25 14:23:33 2017] Started Thread-2
[Fri Aug 25 14:23:33 2017] Started Thread-3
[Fri Aug 25 14:23:35 2017] Completed Thread-1 (3 secs)
(remaining:Thread-3,Thread-2)
[Fri Aug 25 14:23:36 2017] Completed Thread-3 (3 secs)
(remaining:Thread-2)
[Fri Aug 25 14:23:37 2017] Completed Thread-2 (4 secs)
(remaining:None)
all done at: Fri Aug 25 14:23:37 2017

信号量

信号量是一个表示资源的计数器,当资源消耗时减少,资源释放时增加。当一个线程对资源完成操作后,应该将资源放回资源池中。python将所有的消耗返回简化命名为和锁一样,acquire和release。信号量很灵活,有多个线程都可以分别拥有有限资源的一个实例。

threading模块中有两种信号量类:Semaphore和BoundSemaphore。信号量就是一个有限资源的计数器,当分配一个单位的资源时,计数器减1,当一个单位的资源返回资源池时,计数值加1。BoundSemaphore就像名字表示的,它的值有一个边界,永远不会超过它的初识值,也就是说信号量的释放一定会小于获得的次数,防止异常。

举个栗子

我们模拟将东西放进和拿出的操作,用BoundSemaphore来监控资源池,防止空的还能拿出东西这类异常。

from threading import Thread, Lock, BoundedSemaphore
from random import randrange
from atexit import register
from time import sleep, ctime

lock = Lock()  # 获取锁对象
MAX = 5  # 资源池中的资源数量
resources_pool = BoundedSemaphore(MAX)  # 获取信号量


def refill():
    # 放入资源函数
    with lock:
        print("Refilling resource...")
        try:
            resources_pool.release()  # 将资源释放放入资源池
        except ValueError:
            print("Full, skipping...")
        else:
            print("OK!")


def take_out():
    with lock:
        print("Taking out resource...")
        if resources_pool.acquire(False):  # 资源消耗,分配给这个线程
            print("OK!")
        else:
            print("Empty, skipping...")


def producer(loops):  # 调用放回资源的对象,可以理解为资源生产者
    for i in range(loops):
        refill()
        sleep(randrange(3))


def consumer(loops):  # 消耗资源的对象
    for i in range(loops):
        take_out()
        sleep(randrange(3))


def main():
    print("Starting ar:", ctime())
    nloops = randrange(2, 6)  # 随机产生线程消耗的资源量
    print("The resources pool full with %d." % MAX)
    Thread(target=consumer, args=(randrange(nloops, nloops + MAX + 2),)).start()  # 开启返回资源池的线程
    Thread(target=producer, args=(nloops,)).start()  # 消耗资源的线程


@register
def _atexit():
    print("All done at:", ctime())  # 输出结束时间


if __name__ == '__main__':
    main()

结果示例:(结果随机产生)

Starting ar: Fri Aug 25 17:30:15 2017
The resources pool full with 5.
Taking out resource...
OK!
Refilling resource...
OK!
Taking out resource...
OK!
Taking out resource...
OK!
Refilling resource...
OK!
Taking out resource...
OK!
Taking out resource...
OK!
All done at: Fri Aug 25 17:30:19 2017
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值