多线程编程之线程安全问题

1,732 阅读3分钟

在多线程环境下,访问相同的资源,有可能会引发线程不安全的问题。这一篇着重讨论这些问题的根源和解决方法。 线程安全是多线程或多进程编程中的一个概念,在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程可以正常并且正确的执行,不会出现数据污染等意外情况。

1.临界资源问题

多个线程同时运行,有时线程之间需要共享数据,一个线程需要其他线程的数据,否则就无法保证程序运行结果的正确性

例如12306网站,每天的火车票数量是有限的,很多售票网点同时销售这些车票。简单模拟这个系统

import threading
import time
import random
​
​
class TicketDB:
    def __init__(self):
        """
        车票数量
        """
        self.ticket_count = 5
​
    def get_ticket_count(self):
        """
        获取当前车票的数量
        :return: 当前车票的数量
        """
        return self.ticket_count
​
    def sell_ticket(self):
        # TODD:用户购票付款流程
        sleep_time = random.randrange(1, 8)
        time.sleep(sleep_time)
        t = threading.current_thread()
        print(f'{t.name}网点,已经售出第{self.ticket_count}张票')
        self.ticket_count -= 1
​
​
db = TicketDB()
​
​
def thread_body():
    global db
    while True:
        curr_ticket_count = db.get_ticket_count()
        if curr_ticket_count > 0:
            db.sell_ticket()
        else:
            break
​
​
def main():
    t1 = threading.Thread(target=thread_body)
    t1.start()
    t2 = threading.Thread(target=thread_body)
    t2.start()
​
​
if __name__ == '__main__':
    main()

result:

Thread-2网点,已经售出第5张票
Thread-1网点,已经售出第4张票
Thread-2网点,已经售出第3张票
Thread-1网点,已经售出第2张票
Thread-2网点,已经售出第1张票
Thread-1网点,已经售出第0张票

每次运行结果都不太一样,但是都会存在一些问题:总共只有5张票,但是却卖了6次。其根本原因是多个线程间数据共享导致了数据的不一致性,这就是临街资源问题。

多个线程间共享的数据称为共享资源或者临界资源,由于CPU负责线程的调度,程序无法精确控制多线程的交替顺序。这种情形下,多线程对临街资源的访问有时会导致数据的不一致性。想要解决这个问题就必须通过锁来保障线程切换的时机。需要注意的是,list、tuple、dict本身就是属于线程安全的,如果多线程对这三种容器多操作,就不会有上述问题。

2 多线程同步

为了防止多线程对临界资源的访问导致的数据不一致性,python提供了“互斥”机制。简单来说,就是为资源对象加上一把“互斥锁”,在任一时刻只能由一个线程访问,即使该线程出现阻塞,该对象的被锁定状态也不会解除,其他线程仍然不能访问该对象,这就是多线程同步。线程同步是保证线程安全的重要手段,但是线程同步客观上会导致性能下降。

python中线程同步可以使用threading模块的Lock类。Lock对象有锁定和未锁定这两种状态,默认是未锁定状态。Lock对象有两个方法实现锁定和解锁。其中,acquire()方法实现锁定,使得Lock对象进入锁定状态;release()方法实现解锁,使得Lock对象进入未锁定状态。

threading模块中提供了最常见的5种锁,具体划分如下:

同步锁:lock(一次只能放行一个)

递归锁:rlock(一次只能放行一个)

条件锁: condition(一次可以放行任意多个)

事件锁:event(一次全部放行)

信号量锁:semaphore(一次可以放行特定个)