跳至主要內容

16. 线程与多线程编程

小白debug大约 6 分钟

16. 线程与多线程编程

今天我们要聊聊一个有趣而又实用的主题: 线程与多线程编程

在我们开始之前,让我们先放轻松,别担心,这个话题并不难理解,而且对于你写出更高效的程序非常有用。

什么是线程?

首先,让我们来解释一下什么是线程。在计算机中,线程可以看作是执行程序的一条执行路径,一个程序可以同时运行多个线程,每个线程负责不同的任务。

举个例子,想象你在玩一个游戏,同时还可以听音乐,这就是多线程的感觉,你可以同时做多件事情。

为什么需要多线程?

有了线程,我们可以同时处理多个任务,这样可以提高程序的效率和响应速度。

比如在一个聊天应用中,你可以同时接收消息、发送消息、显示聊天记录等等。如果所有这些任务都在一个线程中处理,可能会导致程序变得很慢,甚至卡死。

使用线程

接下来,我们来看一下如何在 Python 中使用线程。Python 提供了内建的 threading 模块来支持多线程编程。

首先,我们需要导入 threading 模块:

import threading

接着,我们可以创建一个线程:

def print_numbers():
    for i in range(5):
        print(i)

# 创建一个线程
thread = threading.Thread(target=print_numbers)

现在,我们可以启动这个线程:

thread.start()

这样,线程就开始运行了。在这个例子中,线程会打印出数字 0 到 4。

为什么要有线程同步?

在多线程编程中,有时候会出现多个线程同时访问一个共享资源的情况,这时候就可能会导致数据错乱。

举个例子,假如你在一个游戏中有一个共享的金币变量,同时有两个线程在进行游戏,一个在增加金币,一个在减少金币。如果没有线程同步,就有可能出现错误的结果。

# 错误示例
def increase_gold():
    global gold
    gold += 10

def decrease_gold():
    global gold
    gold -= 5

gold = 100

thread1 = threading.Thread(target=increase_gold)
thread2 = threading.Thread(target=decrease_gold)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(gold)  # 可能得到的结果并不是 105

使用锁以保证线程安全

为了解决上面的问题,我们可以使用线程同步的技术,Python 提供了 threading.Lock 来帮助我们实现线程同步。

lock = threading.Lock()

def increase_gold():
    global gold
    lock.acquire()  # 获取锁
    gold += 10
    lock.release()  # 释放锁

def decrease_gold():
    global gold
    lock.acquire()  # 获取锁
    gold -= 5
    lock.release()  # 释放锁

gold = 100

thread1 = threading.Thread(target=increase_gold)
thread2 = threading.Thread(target=decrease_gold)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(gold)  # 现在得到的结果是 105

其他线程同步方案

除了使用锁,还有其他的线程同步方案,比如信号量(threading.Semaphore)和事件(threading.Event)。

信号量(threading.Semaphore

信号量是一个允许多个线程同时访问共享资源,但限制同时访问的线程数量的工具。

import threading
import time

# 共享资源
shared_resource = 0

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

# 线程函数:访问共享资源
def access_resource():
    global shared_resource
    for _ in range(5):
        with lock:
            shared_resource += 1
            print(f'线程 {threading.current_thread().name} 访问共享资源,当前值为 {shared_resource}')
            time.sleep(1)

# 创建两个线程
thread1 = threading.Thread(target=access_resource, name='Thread 1')
thread2 = threading.Thread(target=access_resource, name='Thread 2')

# 启动线程
thread1.start()
thread2.start()

# 等待两个线程结束
thread1.join()
thread2.join()

print('所有线程执行完毕')

在这个例子中,我们创建了一个共享资源 shared_resource,并使用了一个锁 lock 来保证在同一时刻只有一个线程可以访问共享资源。每个线程都会在 access_resource 函数中访问共享资源,每次访问后会暂停 1 秒钟,以模拟一些复杂的操作。

通过这个例子,我们可以清晰地看到两个线程如何交替地访问共享资源,而不会产生竞态条件。

事件(threading.Event

事件用于线程间的通信,一个线程发送事件,其他线程等待事件。

import threading
import time

# 创建一个事件
event = threading.Event()

# 线程函数:等待事件并执行任务
def wait_for_event():
    print('线程等待事件')
    event.wait()  # 阻塞直到事件被设置为True
    print('事件已触发,执行任务')

# 触发事件
def trigger_event():
    print('事件将在两秒后触发')
    time.sleep(2)
    event.set()  # 设置事件为True,唤醒等待中的线程

# 创建线程
thread1 = threading.Thread(target=wait_for_event)
thread2 = threading.Thread(target=trigger_event)

# 启动线程
thread1.start()
thread2.start()

# 等待两个线程结束
thread1.join()
thread2.join()

print('所有线程执行完毕')

在这个例子中,我们创建了一个事件 event,线程 wait_for_event 会在等待事件触发时执行任务。线程 trigger_event 会在两秒后触发事件。通过事件,我们可以在不同线程之间进行同步,实现一些复杂的协作逻辑。

线程同步方案的比较

  • 锁(threading.Lock):最基本的同步机制,可以保证同一时刻只有一个线程访问共享资源。适用于简单的场景。
  • 信号量(threading.Semaphore):允许多个线程同时访问共享资源,但限制同时访问的线程数量。适用于资源有限的情况。
  • 事件(threading.Event):可以实现线程间的通信,一个线程发送事件,其他线程等待事件。适用于需要协调多个线程工作的情况。

实战例子:多线程下载图片

让我们用一个实际的例子来总结一下吧!假设我们需要从网上下载一批图片,我们可以使用多线程来提高下载速度。

import requests
import threading

def download_image(url, filename):
    response = requests.get(url)
    with open(filename, 'wb') as file:
        file.write(response.content)
    print(f'{filename} 下载完成')

# 图片链接列表
image_urls = [
    'https://example.com/image1.jpg',
    'https://example.com/image2.jpg',
    'https://example.com/image3.jpg'
]
# 创建线程来下载图片
threads = []
for i, url in enumerate(image_urls):
    filename = f'image_{i}.jpg'
    thread = threading.Thread(target=download_image, args=(url, filename))
    threads.append(thread)
    thread.start()

# 等待所有线程结束
for thread in threads:
    thread.join()

print('所有图片下载完成')

通过这个例子,我们学会了如何使用多线程来加速任务的执行,这在网络请求等 IO 密集型任务中非常实用。

小结

通过这篇文章,我们学习了如何在 Python 中使用线程以及线程同步的重要性。我们了解了锁、信号量和事件等线程同步方案,并通过一个实际的例子加深了理解。

希望你现在对线程和线程同步有了更清晰的认识。继续加油,你已经掌握了写出高效程序的一部分诀窍!

如果你学会了如何使用线程,你将能够写出更加高效、响应速度更快的程序,这对于处理大量任务或者高并发的情况非常重要。同时,理解了线程同步,可以避免因多线程访问共享资源而产生的问题,确保程序的稳定性和可靠性。

训练营看视频