首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python 多线程编程(二):threading 模块中 Lock 类的用法详解

Python 多线程编程(二):threading 模块中 Lock 类的用法详解

作者头像
Frank909
发布2019-01-14 17:39:17
1K0
发布2019-01-14 17:39:17
举报
文章被收录于专栏:Frank909Frank909

在前面一篇博文《Python多线程编程(一):threading 模块 Thread 类的用法详解 》 我有简单介绍怎么利用 threading 模块进行多线程的编码。

但那只是多线程编码最简单的部分,真正难的其实是多个线程之间的通信和数据同步。

大概可以这样讲,多线程最难的是如何正确协调各个线程修改同一份数据。

以卖票的例子作为说明。

买票有多个窗口,假设有 3 个好了,窗口之间共享一个票池,每个窗口都可以买票直到票池里面没有票可以卖。

不难写出下面的代码。

import threading
import random

class WindowThread(threading.Thread):

    def __init__(self,name):
        threading.Thread.__init__(self,name=name)
        self.name = name
        self.tickts = 0

    def run(self):
        global tickt_count

        while tickt_count > 0:

            print('%s notice:There has %d tickts remain ' %(self.name,tickt_count))

            if tickt_count > 2:
                number = random.randint(1,2)
            else:
                number = 1

            tickt_count -= number
            self.tickts += number

            print('%s have buy %d tickt,the remain tickt\'t count is %d .Already buy %d \n'
                  % (self.name, number, tickt_count, self.tickts))

        print('%s notice:There is no tickt can sold! Already sold %d'%(self.name,self.tickts))


tickt_count = 10

window1 = WindowThread('window1')
window2 = WindowThread('window2')
window3 = WindowThread('window3')

window1.start()
window2.start()
window3.start()
window1.join()
window2.join()
window3.join()

print('tickt count ',tickt_count)

假设有 3 个窗口,总共有 10 张票。

在每个窗口排队购票时,窗口会告诉你当前还剩多少票。

if tickt_count > 2:
      number = random.randint(1,2)
   else:
      number = 1

tickt_count -= number
self.tickts += number

每个人最多购买 2 张票,如果票池当中只剩下 1 张票时,那么也只能购买一张票了。

tickt_count 是票池里面剩余的票的数量。 tickts 是每个窗口已经卖出的数量。

购买结束后,要更新票池里面剩余票的数量,和更新本窗口卖出去的票数。

最后,所有的窗口无票可卖的时候,结束。然后打印 票池里面的票数,理论上应该是 0,因为票已经卖光了。

我们看结果运行如何:

window1 notice:There has 10 tickts remain 
window2 notice:There has 10 tickts remain 
window1 have buy 2 tickt,the remain tickt't count is 8 .Already buy 2 
window2 have buy 1 tickt,the remain tickt't count is 7 .Already buy 1 

window1 notice:There has 7 tickts remain 
window1 have buy 2 tickt,the remain tickt't count is 5 .Already buy 4 

window1 notice:There has 5 tickts remain 
window1 have buy 1 tickt,the remain tickt't count is 4 .Already buy 5 

window1 notice:There has 4 tickts remain 
window1 have buy 1 tickt,the remain tickt't count is 3 .Already buy 6 

window1 notice:There has 3 tickts remain 
window1 have buy 2 tickt,the remain tickt't count is 1 .Already buy 8 

window3 notice:There has 1 tickts remain 
window1 notice:There has 1 tickts remain 
window1 have buy 1 tickt,the remain tickt't count is 0 .Already buy 9 

window1 notice:There is no tickt can sold! Already sold 9
window3 have buy 1 tickt,the remain tickt't count is -1 .Already buy 1 

window3 notice:There is no tickt can sold! Already sold 1

window2 notice:There is no tickt can sold! Already sold 1
tickt count  -1

多运行几次,可以发现一个现象,那就是结果不正确。

3 个窗口的卖出的总数对不上。

最后显示 tickt count 为 -1,而不是 0,这显然不符合我们的预期。

Q:为什么会这样呢?

A:因为我们没有保护好多个线程之间,共享的数据。

Q:怎么能解决呢?

A:Lock 机制可以解决

什么是 Lock?

Lock 中文称为锁,是一种初级的多线程同步的手段。

Lock 有 locked 和 unlocked 两种状态,而这两中状态之间是可以转换的.

  • 当 Lock 是 unlocked 状态时候,某个线程调用 acquire() 可以获取这个 Lock,并且将 Lock将状态转换成 locked 状态,并且线程不会阻塞。
  • 但当 Lock 是 locked 状态时,某个线程调用 acquire() 会阻塞自己,直到其他的线程将 Lock 的状态变成 unlocked。
  • 当 Lock 是 locked 状态时,调用 release() 方法,可以释放一个 Lock,这样其它线程就可以获取这个 Lock 了。
  • 但当 Lock 是 unlocked 状态时,某个线程调用 release(),程序会抛出 RuntimeError 异常。

所以,acquire() 和 release() 方法在单个线程当中都是成对使用的。

在这里插入图片描述
在这里插入图片描述

有效利用 Lock 的状态转换机制,就可以避免多个线程同时修改同一份数据。

于是,我们可以进行代码的改写。

import threading
import random


class WindowThread(threading.Thread):

    def __init__(self,name,lock):
        threading.Thread.__init__(self,name=name)
        self.name = name
        self.tickts = 0
        self.lock = lock

    def run(self):
        global tickt_count

        while tickt_count > 0:

            print('%s notice:There has %d tickts remain ' %(self.name,tickt_count))

            self.lock.acquire()
            if tickt_count > 0:
                if tickt_count > 2:
                    number = random.randint(1,2)
                else:
                    number = 1
                tickt_count -= number
                self.tickts += number

                print('%s have buy %d tickt,the remain tickt\'t count is %d .Already buy %d \n'
                      % (self.name, number, tickt_count, self.tickts))
            self.lock.release()


        print('%s notice:There is no tickt can sold! Already sold %d'%(self.name,self.tickts))


tickt_count = 10

lock = threading.Lock()

window1 = WindowThread('window1',lock)
window2 = WindowThread('window2',lock)
window3 = WindowThread('window3',lock)

window1.start()
window2.start()
window3.start()
window1.join()
window2.join()
window3.join()

print('tickt count ',tickt_count)

还是 3 个线程,但代码少许不一样。

lock = threading.Lock()

window1 = WindowThread('window1',lock)
window2 = WindowThread('window2',lock)
window3 = WindowThread('window3',lock)

3 个线程共用 1 个 Lock 对象。

self.lock.acquire()
if tickt_count > 0:
   if tickt_count > 2:
       number = random.randint(1,2)
   else:
       number = 1
   tickt_count -= number
   self.tickts += number

   print('%s have buy %d tickt,the remain tickt\'t count is %d .Already buy %d \n'
         % (self.name, number, tickt_count, self.tickts))
self.lock.release()

进行关键数据操作的时候,用 Lock 锁起来,这样每次就只能一个线程对 tickt_count 数量进行修改。

最终程序结果运行如下:

window1 notice:There has 10 tickts remain 
window1 have buy 1 tickt,the remain tickt't count is 9 .Already buy 1 

window2 notice:There has 9 tickts remain 
window1 notice:There has 9 tickts remain 
window2 have buy 1 tickt,the remain tickt't count is 8 .Already buy 1 

window3 notice:There has 8 tickts remain 
window2 notice:There has 8 tickts remain 
window2 have buy 2 tickt,the remain tickt't count is 6 .Already buy 3 

window2 notice:There has 6 tickts remain 
window3 have buy 2 tickt,the remain tickt't count is 4 .Already buy 2 

window3 notice:There has 4 tickts remain 
window2 have buy 2 tickt,the remain tickt't count is 2 .Already buy 5 

window2 notice:There has 2 tickts remain 
window1 have buy 1 tickt,the remain tickt't count is 1 .Already buy 2 

window1 notice:There has 1 tickts remain 
window3 have buy 1 tickt,the remain tickt't count is 0 .Already buy 3 

window3 notice:There is no tickt can sold! Already sold 3
window2 notice:There is no tickt can sold! Already sold 5
window1 notice:There is no tickt can sold! Already sold 2
tickt count  0

可以多试几次,窗口卖出的票数都是可以对的上号的,并且最终票池里面的数量是 0,不会发生之前出现为 -1 的情况。

所以,自此我们就通过引入 Lock 同步机制,进行了一个很简单化的多线程编码示例。

默认情况,当一个 Lock 是 locked 状态时调用 acquire(),会阻塞线程本身。

但我们可以设置不阻塞,或者是阻塞指定时间。

#不阻塞
lock.acquire(False)

#阻塞指定时间,如 3 秒钟,当然 python3 的版本才有这个功能
lock.acquire(timeout=3)
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年12月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 Lock?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档