首页 > 编程笔记 > Python笔记 阅读:2

Python threading多线程模块的用法(附带实例)

多个进程可以实现多个任务,一个进程内的多个线程(多线程)同样可以实现多个任务。

进程是由若干个线程组成的。一个进程中至少要有一个线程。同一个进程内的多个线程资源共享。线程之间的切换开销比多个进程的切换开销更低。

Python threading模块

Python 通过 threading 模块提供了对多线程的支持,使用 threading 模块创建线程的方法有:
threading 模块提供的方法有:
首先创建一个 Thread 实例并传入函数,然后调用 start() 即可启动一个新的线程,代码为:
import time
import threading

def thread1():
    while True:
        print('thread1 is running')
        time.sleep(2)

if __name__ == '__main__':
    t1 = threading.Thread(target=thread1, name="")
    t1.start()
    while True:
        print('Main Thread is running')
        time.sleep(2)
任何进程都会默认启动一个线程,也就是主线程。执行程序代码后,可以看到主线程和子线程一直处于运行状态,即:

thread1 is running
Main Thread is running
Main Thread is running
thread1 is running
Main Thread is running
thread1 is running
Main Thread is running

Python threading线程同步

主线程和子线程之间相互独立,两者之间没有任何关系。

现在假设有一个水池,现有的水量为 1000。有两个线程:
各自重复执行 100000 次,查看水池里的水量。测试代码为:
import threading
import time

water = 1000

def add_water():
    global water
    for i in range(100000):
        water += 1

def sub_water():
    global water
    for i in range(100000):
        water -= 1

if __name__ == '__main__':
    t_add = threading.Thread(target=add_water, name="")
    t_sub = threading.Thread(target=sub_water, name="")
    t_add.start()
    t_sub.start()
    t_add.join()
    t_sub.join()
    print(water)
多次执行以上程序后,得到的结果为:

1000
-24335
91567

从理论上来讲,加入的水量和抽出的水量相同,多次操作之后,水量应当是原始值,也就是 1000。为什么会出现 -24335 和 91567 呢?是因为 water+=1,也就是 water = water + 1,程序的执行分为两步:
water 值的计算从 CPU 的角度需要多个步骤,每个步骤的线程都可能被中断,则多个线程就会把同一个对象的内容改乱。为了避免这样的混乱产生,threading 模块提供了多种机制实现线程间的同步。其中,Lock(锁)就是常用的方法之一。

当一个线程修改 water 值时,通过加锁操作可防止其他线程同时进行修改操作,从而避免了 water 值被改错,代码为:
import threading
import time

water = 1000
lock = threading.Lock()

def add_water():
    global water
    lock.acquire()
    for i in range(100000):
        water += 1
    lock.release()

def sub_water():
    with lock:
        global water
        for i in range(100000):
            water -= 1

if __name__ == '__main__':
    t_add = threading.Thread(target=add_water, name="")
    t_sub = threading.Thread(target=sub_water, name="")
    t_add.start()
    t_sub.start()
    t_add.join()
    t_sub.join()
    print(water)
以上是使用 Lock 之后的程序代码,多次执行后,水量总是 1000,不会被改乱。

除了 Lock,threading 模块还提供了 RLock、Semaphore 等多种同步机制。

Python threading线程间通信

与多进程一样,Python 的 threading 模块同样提供了队列(Queue)、管道(Pipe)等多线程间的通信方式。在 Python 中,队列是线程间最常用的交换数据形式。Queue 是线程安全的自带锁,使用时,不用对队列进行加锁操作。

接下来以生产者和消费者为模型,讲解 threading 模块 Queue 的使用。

Queue 用于建立和操作队列,常与 threading 模块一起建立一个简单的线程队列。队列有很多种,根据进出顺序可以分为:
其中,FIFO 是最常用的队列,常用的方法有:
接下来编写一个程序代码:一个线程(生产者)生成数据并将数据加入队列;另一个线程(消费者)去队列中提取这些数据,程序代码保存在文件 queue_thread.py 中,即:
import threading, time
import queue

q = queue.Queue()

def Producer():
    n = 0
    while n < 5:
        n += 1
        q.put(n)
        print('Producer has created %s' % n)
        time.sleep(0.1)

def Consumer():
    count = 0
    while count < 5:
        count += 1
        data = q.get()
        print('Consumer has used %s' % data)
        time.sleep(0.2)

p = threading.Thread(target = Producer, name="")
c = threading.Thread(target = Consumer, name="")
运行结果为:

Producer has created 1
Consumer has used 1
Producer has created 2
Consumer has used 2
Producer has created 3
Producer has created 4
Consumer has used 3
Producer has created 5
Consumer has used 4
Consumer has used 5

由运行结果可知,生产者线程依次生成 1~5 的整数,消费者线程依次提取 5 个数字,两者交替进行。

相关文章