Python中的线程模块之线程同步和队列(第4节)


Python线程模块中的线程同步是指协调和管理多个线程的执行顺序和访问共享资源的方式,以确保数据的完整性和一致性。

1、线程同步

在Python多线程环境中,当多个线程共同修改或者操作同一个对象或者数据时,如果线程需要共享数据的话,可能存在数据不同步的问题。例如:

动手练一练:

import time
import threading

# 从threading.Thread类派生一个子类MyThread
class MyThread(threading.Thread):
    def __init__(self, number):
        # 调用父类的构造函数
        super(MyThread,self).__init__()
        self.number = number

    # 重写父类的run方法
    def run(self):
         # for循环打印三次数据
        for i in range(3):
            print("第", self.number, "个线程开始执行时间:", time.ctime())
            time.sleep(2)
def main():
    datas = []
    #创建3个线程
    for i in range(3):
        thread = MyThread(i)
        datas.append(thread)

    #启动3个线程
    for i in range(3):
        datas[i].start()

    #等待3个线程执行完毕
    for i in range(3):
        datas[i].join()

#当前模块直接被执行
if __name__ == "__main__":
    main()

执行以上代码,输出结果为:

 0 个线程开始执行时间 Wed May 1 18:57:36 2024
 1 个线程开始执行时间 Wed May 1 18:57:36 2024
 2 个线程开始执行时间 Wed May 1 18:57:36 2024
 1 个线程开始执行时间 Wed May 1 18:57:38 2024
 2 个线程开始执行时间 Wed May 1 18:57:38 2024
 0 个线程开始执行时间 Wed May 1 18:57:38 2024
 1 个线程开始执行时间 Wed May 1 18:57:40 2024
 0 个线程开始执行时间 Wed May 1 18:57:40 2024
 2 个线程开始执行时间 Wed May 1 18:57:40 2024

上面的例子中,我们创建了3个线程,每个线程隔2秒输出一条数据。可以明显看出,每次输出3个线程的顺序都不相同,而且3个线程每次输出的时间会重叠,因此为了保证数据的正确性,需要对3个线程进行同步。使用threading对象的Lock方法可以实现简单的线程同步,该对象包括acquire()方法锁定线程和release()方法释放线程,可以实现每次只允许一个线程操作数据。如下:

动手练一练:

import time
import threading

#获取锁
lock = threading.Lock()

# 从threading.Thread类派生一个子类MyThread
class MyThread(threading.Thread):
    def __init__(self, number):
        # 调用父类的构造函数
        super(MyThread,self).__init__()
        self.number = number

    # 重写父类的run方法
    def run(self):
        #锁定
        lock.acquire()
         # for循环打印三次数据
        for i in range(3):
            print("第", self.number, "个线程开始执行时间:", time.ctime())
            time.sleep(2)
        #释放
        lock.release()

def main():
    datas = []
    #创建3个线程
    for i in range(3):
        thread = MyThread(i)
        datas.append(thread)

    #启动3个线程
    for i in range(3):
        datas[i].start()

    #等待3个线程执行完毕
    for i in range(3):
        datas[i].join()

#当前模块直接被执行
if __name__ == "__main__":
    main()

执行以上代码,输出结果为:

 0 个线程开始执行时间 Wed May 1 18:53:36 2024
 0 个线程开始执行时间 Wed May 1 18:53:38 2024
 0 个线程开始执行时间 Wed May 1 18:53:40 2024
 1 个线程开始执行时间 Wed May 1 18:53:42 2024
 1 个线程开始执行时间 Wed May 1 18:53:44 2024
 1 个线程开始执行时间 Wed May 1 18:53:46 2024
 2 个线程开始执行时间 Wed May 1 18:53:48 2024
 2 个线程开始执行时间 Wed May 1 18:53:50 2024
 2 个线程开始执行时间 Wed May 1 18:53:52 2024

上面的例子中,对线程加锁后,只有当第1个线程的函数执行完,才会运行第2个线程的线程函数,以此类推。当子线程运行到lock.acquire()的时候,程序会判断lock是否处于锁定状态,如果处于锁定状态的话,就会让线程暂停,也就是同步阻塞,直到锁被释放为止。虽然3个线程“同时”在执行,但只有一个线程执行完以后才会执行下一个线程。

2、队列

在Python中,多个线程之间的数据是共享的,多个线程进行数据交换的时候,不能够保证数据的安全性和一致性,所以当多个线程需要进行数据交换的时候,队列就出现了,队列可以完美解决线程间的数据交换,保证线程间数据的安全性和一致性。Queue模块为我们提供了同步的、线程安全的队列类,利用先进先出法则帮助我们自动控制锁,保证数据同步。

Queue类使用put()方法将一个元素加入到队列的尾端,使用get()方法从队列头部中移除并返回一个元素。例如:

动手练一练:

import queue

# 创建一个Queue对象
q = queue.Queue()

# 向队列中添加两个元素
q.put('E')
q.put('F')

# 从队列中获取第一个元素
a = q.get()
print(a)  # 输出: 'E'

# 从队列中获取第二个元素
b = q.get()
print(b)  # 输出: 'F'

# 查看队列大小
size = q.qsize()
print(size)  # 输出: 0

# 检查队列是否为空
is_empty = q.empty()
print(is_empty)  # 输出: True

执行以上代码,输出结果为:

E
F
0
True

上面的例子中,演示了使用单线程将元素按插入顺序从队列中移除。在多线程中使用Queue模块,可以帮助我们实现在线程间传递、共享数据。多个线程可以共同使用同一个Queue实例。例如:

动手练一练:

import queue
import time
import threading

# 创建一个特殊的终止值
stops = object()

# 创建queue队列
myQueue = queue.Queue()

# 生产者线程
def producer(a):
    n = 0
    while n < 5:
        n += 1
        myQueue.put('队列{0}'.format(n))
    myQueue.put(stops)

# 消费者线程
def consumer(b):
    while True:
        # block参数设置为False,如果队列为空,直接引发queue.Empty异常
        data = b.get(block=False)
        # 遇到特殊的终止值,结束循环,并退出线程
        if data is stops:
            b.put(stops)
            break
        print(data)
        time.sleep(1)

# 启动生产者和消费者线程
threading.Thread(target=producer, args=(myQueue,)).start()
threading.Thread(target=consumer, args=(myQueue,)).start()

执行以上代码,输出结果为:

队列1
队列2
队列3
队列4
队列5

上面的例子中,模拟生产者和消费者之间线程安全地传递数据。我们创建了一个Queue对象,并将其命名为myQueue,生产者线程使用myQueue.put()方法向队列中放入数据,消费者线程使用myQueue.get()方法从队列中取出数据进行处理。使用多线程和Queue作为同步工具,可以实现生产者和消费者之间安全地传递数据。由于Queue为线程安全的类型,所以在添加和移除数据的过程中会自动获取所需的锁。