Python多线程(三):锁

上一篇:多线程编程

Python多线程(一):GIL中我们提到了竞态条件问题,即不同线程修改相同的共享变量出现运行多次结果不一样的问题,即使CPython中有GIL,这种问题依然存在。现在我们通过多线程的锁机制来解决这个问题。

还是相同的代码:

import threading

total = 0
def add():
    global total
    for i in range(1000000):
        total += 1

def desc():
    global total
    for i in range(1000000):
        total -= 1

thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(total)

之前我们的分析的原因在于:两加法减法操作在底层实现的时候有多个步骤,由于GIL的切换导致字节码交替运行。如果我们能够保证实现加法或者减法操作的时候只有一个线程在运行,就能解决这个问题。而保证某一代码段只有一个线程在运行的方法就是为这个线程加锁,如下所示:

import threading

total = 0
lock = threading.Lock()
def add():
    global total
    global lock
    for i in range(1000000):
        lock.acquire()
        total += 1
        lock.release()

def desc():
    global total
    global lock
    for i in range(1000000):
        lock.acquire()
        total -= 1
        lock.release()

thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(total)

运行结果为0。在上面的代码中,threading.Lock()实例化了一个锁对象,锁对象有两个方法:acquirerelease,分别是获得锁和释放锁。当一个线程获得所时,另外一个线程在acquire处阻塞,直到当前锁执行release被释放后才可以和其他线程共同争夺锁。acquirerelease之间的代码段执行时不会切换到其他线程,保证了操作的完整性。

用锁也存在问题,首先就是性能问题,在上面的例子中,不使用锁运行的执行时间是0.15秒,而使用锁执行时间是2.35秒,足足慢了15倍。

另外一个问题被称为死锁。当一个线程调用子程序时,如果这个子程序也需要加锁,则会出现这个问题:

import threading
import time

lock = threading.Lock()

def do_something():
    global lock
    lock.acquire()
    do_sub_task()
    lock.release()

def do_sub_task():
    global lock
    lock.acquire()
    time.sleep(2)
    lock.release()

thread = threading.Thread(target=do_something)
thread.start()
thread.join()

程序会在do_sub_task的首句阻塞,因为该函数试图去获取锁,但是锁并没有释放。解决方法有两种:

  • 一种是通过threading.Lock()再实例化一把锁,使得do_somethingdo_sub_task所需要的锁不是同一把,这样即使do_something获取了锁,do_sub_task也能够获得另外的锁。但是这种方式的问题是当这种情况出现很多,锁就很难管理。
  • 另外一种是使用threading.RLock,这种锁可以重复获得,只要释放的次数等于获得的次数即可。将上面代码中的Lock换成RLock即可。

还有一种死锁情况称为互相等待,参看下面代码:

import threading
import time

lock1 = threading.Lock()
lock2 = threading.Lock()

def do_something1():
    lock1.acquire()
    time.sleep(2)
    lock2.acquire()
    print('Something 1 started')
    time.sleep(2)
    lock1.release()
    lock2.release()
    print('Something 1 ended')

def do_something2():
    lock2.acquire()
    time.sleep(3)
    lock1.acquire()
    print('Something 2 started')
    time.sleep(3)
    lock2.release()
    lock1.release()
    print('Something 2 ended')

thread1 = threading.Thread(target=do_something1)
thread2 = threading.Thread(target=do_something2)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

这个程序会一致阻塞,原因在于两个线程获得两个锁的顺序是相反的,当do_something1运行时获得lock1,然后执行time.sleep(2)使得GIL释放去执行do_something2do_something2获得lock2后,同样执行time.sleep(3)使得GIL释放去执行do_something1do_something1此时需要获得lock2才能继续执行,然而lock2do_something2处,未释放无法获得。同理do_something2需要获得的lock1do_something1处,也无法获得。所以就出现了两个线程互相等待的情况。如果将其中某个线程获得的锁的顺序交换,程序就能正常执行。

可以看出,使用锁机制很容易造成死锁,在使用锁的时候一定要小心。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容

  • 一. 操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式: 向下管理硬件,向上提供接口.操作系统进行...
    月亮是我踢弯得阅读 5,942评论 3 28
  • 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0.1秒,...
    妄心xyx阅读 912评论 0 30
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,626评论 0 6
  • 当兵以后,不管何时再听着这首歌,都会无限忧郁在心头,我是一个顽皮叛逆的思想者,因为比较孤独,高中阶段形成了特别思想...
    中星道易阅读 1,039评论 0 0
  • 对警察的最初印象,来自我的爷爷。 记忆中的他,高大魁梧,虽然慈眉善目、但是不怒自威,虽然头发花白、但是眼神矍铄,因...
    bq进化论阅读 121评论 0 1