Python的多线程使用

首先,你需要知道什么是线程。线程是一个执行流,也就是说,它是一个程序中的一段代码,可以独立地运行。一个程序可以有多个线程,这样就可以同时执行多个任务。比如,你在浏览网页的时候,可能有一个线程负责显示网页内容,另一个线程负责加载图片,还有一个线程负责处理用户的输入。这样就可以提高程序的效率和用户体验。

Python中有两个模块可以用来创建和管理线程,一个是_thread模块,另一个是threading模块。_thread模块是比较低级的,功能比较简单,而threading模块是基于_thread模块的高级封装,提供了更多的功能和易用性。所以,一般来说,我们推荐使用threading模块来进行多线程编程。

要使用threading模块创建线程,有两种方法:

  • 1.方法一:定义一个函数,然后用threading.Thread类创建一个线程对象,并把函数作为参数传给它。然后调用线程对象的start()方法来启动线程。例如:

import threading
import time

# 定义一个函数
def print_time(thread_name, delay):
    # 循环5次
    for i in range(5):
        # 每次循环前暂停delay秒
        time.sleep(delay)
        # 打印当前时间和线程名
        print(thread_name, time.ctime())

# 创建两个线程对象
t1 = threading.Thread(target=print_time, args=("Thread-1", 1))
t2 = threading.Thread(target=print_time, args=("Thread-2", 2))

# 启动两个线程
t1.start()
t2.start()

# 等待两个线程结束
t1.join()
t2.join()

# 打印主线程结束
print("Main thread finished")
  • 2.方法二:定义一个类,继承自threading.Thread类,并重写它的__init__()方法和run()方法。init()方法用来初始化线程对象的属性,run()方法用来定义线程要执行的任务。然后创建类的实例,并调用start()方法来启动线程。例如:

import threading
import time

# 定义一个类,继承自threading.Thread类
class MyThread(threading.Thread):
    # 重写__init__()方法
    def __init__(self, thread_name, delay):
        # 调用父类的__init__()方法
        threading.Thread.__init__(self)
        # 初始化属性
        self.thread_name = thread_name
        self.delay = delay
    
    # 重写run()方法
    def run(self):
        # 调用自定义的函数
        print_time(self.thread_name, self.delay)

# 定义一个函数
def print_time(thread_name, delay):
    # 循环5次
    for i in range(5):
        # 每次循环前暂停delay秒
        time.sleep(delay)
        # 打印当前时间和线程名
        print(thread_name, time.ctime())

# 创建两个类的实例
t1 = MyThread("Thread-1", 1)
t2 = MyThread("Thread-2", 2)

# 启动两个线程
t1.start()
t2.start()

# 等待两个线程结束
t1.join()
t2.join()

# 打印主线程结束
print("Main thread finished")

以上两种方法的效果是一样的,都会创建两个子线程,并让它们分别每隔一秒和两秒打印一次当前时间和自己的名字。主线程会等待子线程都结束后再结束。

你可能会注意到,在上面的例子中,我们都使用了join()方法来等待子线程结束。这是因为如果不这样做,主线程可能会在子线程还没结束的时候就退出,导致程序异常。join()方法可以让主线程阻塞,直到子线程结束或者超时。如果不传入超时时间,就会一直等待。

在多线程编程中,有一个很重要的问题就是线程同步。线程同步是指多个线程在访问共享的资源或数据时,需要保证数据的一致性和正确性。比如,如果有两个线程都要修改一个变量的值,那么就需要保证它们不会同时修改,否则就会出现数据混乱的情况。

3.线程锁

为了实现线程同步,Python提供了一个简单的锁对象,也叫互斥锁。锁对象有两种状态:锁定和未锁定。每当一个线程要访问共享数据时,它必须先获得锁定;如果已经有别的线程获得锁定了,那么它就必须等待;当它使用完毕后,它必须释放锁定,以便其他线程也能获得锁定。

Python的threading模块提供了一个Lock类来创建锁对象。Lock类提供了两个方法:

  • acquire(): 尝试获得锁定。如果成功返回True,如果失败(已被其他线程锁定)返回False。
  • release(): 释放锁定。

例如:

import threading
import time

# 创建一个全局变量
num = 0

# 创建一个锁对象
lock = threading.Lock()

# 定义一个函数
def add_num(thread_name, delta):
    # 声明全局变量
    global num
    # 循环10次
    for i in range(10):
        # 尝试获得锁定
        lock.acquire()
        # 修改全局变量
        num += delta
        # 打印当前值和线程名
        print(thread_name, num)
        # 释放锁定
        lock.release()
        # 暂停0.1秒
        time.sleep(0.1)

# 创建两个线程对象
t1 = threading.Thread(target=add_num, args=("Thread-1", 1))
t2 = threading.Thread(target=add_num, args=("Thread-2", -1))

# 启动两个线程
t1.start()
t2.start()

# 等待两个线程结束
t1.join()
t2.join()

# 打印主线程结束和最终值
print("Main thread finished")
print("Final num:", num)

以上程序会创建两个子线程,分别对一个全局变量num进行加一和减一的操作,并打印出每次修改后的值。由于使用了锁对象来保证每次只有一个线程能够修改num,所以最终的结果是num始终为0。

如果不使用锁对象,那么就可能出现这样的情况:假设num初始为0,然后Thread-1执行了num += 1,此时num为1;但是在Thread-1打印之前,Thread-2执行了num -= 1,此时num又变成了0;然后Thread-1打印出num为0,而不是预期的1;接着Thread-2也打印出num为0。这样就造成了数据的不一致性。

4.队列

当然,并不是所有的情况都需要使用锁对象来实现线程同步。有些情况下,我们可以利用Python的特性来避免数据竞争。比如,Python中的队列(queue)模块提供了一个先进先出(FIFO)的数据结构,它可以安全地在多个线程之间共享数据。队列模块提供了两个方法:

  • put(item): 将item放入队列尾部。
  • get(): 从队列头部取出一个元素,并返回。

例如:

import threading
import queue
import time
import random

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

# 定义一个生产者函数
def producer(thread_name):
    # 循环10次
    for i in range(10):
        # 生成一个随机数
        item = random.randint(1, 100)
        # 将随机数放入队列
        q.put(item)
        # 打印生产者名字和随机数
        print(thread_name, "produced", item)
        # 暂停0.5秒
        time.sleep(0.5)

# 定义一个消费者函数
def consumer(thread_name):
    # 循环10次
    for i in range(10):
        # 从队列中取出一个元素
        item = q.get()
        # 打印消费者名字和元素
        print(thread_name, "consumed", item)
        # 暂停1秒
        time.sleep(1)

# 创建两个线程对象,分别是生产者和消费者
t1 = threading.Thread(target=producer, args=("Producer-1",))
t2 = threading.Thread(target=consumer, args=("Consumer-1",))

# 启动两个线程
t1.start()
t2.start()

# 等待两个线程结束
t1.join()
t2.join()

# 打印主线程结束
print("Main thread finished")

以上程序会创建两个子线程,分别是生产者和消费者。生产者每隔0.5秒就会生成一个随机数,并放入队列中。消费者每隔1秒就会从队列中取出一个元素,并打印出来。由于队列是线程安全的,所以不需要使用锁对象来保证数据的一致性。

你可能感兴趣的:(Python基础,python,开发语言)