深入理解Python多线程:方法解析与实践案例

案例1:

l1=[1,2,3,4,5,6]
for i in l1:
要求1:print(i)
要求2:每一个线程的频率不一样,time.sleep(i)
总结:创建多个线程,每个线程打印频率不一样;为了便于区分,每次打印的时候,可以加一个前缀,类似“线程1”、“线程2”、“线程3”…

1. 使用threading.Thread

我们可以创建threading.Thread类的实例来表示一个线程,然后调用它的start()方法来启动线程。这种方法的特点是简单直观,但缺点是每个线程都需要创建一个Thread对象,可能会占用较多的内存资源。

import threading
import time

def print_num(i):
    print(f"线程{i}{i}")
    time.sleep(i)

l1 = [1, 2, 3, 4, 5, 6]
threads = []

for i in l1:
    t = threading.Thread(target=print_num, args=(i,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

2. 使用concurrent.futures.ThreadPoolExecutor

concurrent.futures.ThreadPoolExecutor类提供了一个线程池,可以自动管理线程的创建和销毁。这种方法的优点是更加高效,因为它可以避免频繁地创建和销毁线程。此外,它还提供了submit()方法,可以方便地提交任务到线程池。

from concurrent.futures import ThreadPoolExecutor
import time

def print_num(i):
    print(f"线程{i}{i}")
    time.sleep(i)

l1 = [1, 2, 3, 4, 5, 6]
with ThreadPoolExecutor(max_workers=len(l1)) as executor:
    for i in l1:
        executor.submit(print_num, i)

3. 使用queue.Queue

queue.Queue类提供了一个线程安全的队列,我们可以将任务放入队列中,然后让多个线程从队列中取出任务并执行。这种方法的优点是可以实现任务的生产者-消费者模型,适用于需要处理大量任务的场景。

import queue
import threading
import time

def worker(q):
    while True:
        i = q.get()
        if i is None:
            break
        print(f"线程{i}{i}")
        time.sleep(i)
        q.task_done()

l1 = [1, 2, 3, 4, 5, 6]
q = queue.Queue()
threads = []

for i in l1:
    q.put(i)

for i in range(len(l1)):
    t = threading.Thread(target=worker, args=(q,))
    t.start()
    threads.append(t)

q.join()
for t in threads:
    t.join()

总结:以上三种方法都可以实现多线程,但各有优劣。使用threading.Thread类的方法简单直观,但可能占用较多内存;使用concurrent.futures.ThreadPoolExecutor类的方法更加高效,适合处理大量任务;使用queue.Queue类的方法可以实现生产者-消费者模型,适用于需要处理大量任务的场景。在实际使用中,我们可以根据具体需求选择合适的方法。

案例2:

案例1中如何持续打印?用三种方法实现。

1. 使用threading.Thread

import threading
import time

def print_num(i):
    while True:
        print(f"线程{i}{i}")
        time.sleep(i)

l1 = [1, 2, 3, 4, 5, 6]
threads = []

for i in l1:
    t = threading.Thread(target=print_num, args=(i,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

2. 使用concurrent.futures.ThreadPoolExecutor

from concurrent.futures import ThreadPoolExecutor
import time

def print_num(i):
    while True:
        print(f"线程{i}{i}")
        time.sleep(i)

l1 = [1, 2, 3, 4, 5, 6]
with ThreadPoolExecutor(max_workers=len(l1)) as executor:
    for i in l1:
        executor.submit(print_num, i)

3. 使用queue.Queue

import queue
import threading
import time

def worker(q, stop_event, keep_working):
    while keep_working:  # 使用全局变量来控制是否继续工作
        try:
            i = q.get(timeout=1)  # 设置超时时间,防止线程卡在q.get()上
            if i is None:
                break
            print(f"线程{i}{i}")
            time.sleep(i)
            q.task_done()
        except queue.Empty:
            pass  # 如果队列为空,则继续等待新任务

l1 = [1, 2, 3, 4, 5, 6]
q = queue.Queue()
stop_event = threading.Event()  # 创建一个事件对象来控制线程的停止
keep_working = True  # 全局变量,控制线程是否继续工作
threads = []

# 将任务添加到队列中,并且可以一直添加
for i in range(1000):  # 添加更多的任务到队列中
    q.put(l1[i % len(l1)])

for i in range(len(l1)):
    t = threading.Thread(target=worker, args=(q, stop_event, keep_working))
    t.start()
    threads.append(t)

# 在某个时刻,当你想要停止线程时
# stop_event.set()  # 设置事件,使得worker中的循环结束,从而停止线程
# keep_working = False  # 设置全局变量,控制线程是否继续工作

# 等待队列中的任务被处理完
q.join()

# 停止工作线程
for i in range(len(l1)):
    q.put(None)
for t in threads:
    t.join()

在这个修改后的代码中,我们添加了一个全局变量 keep_working 来控制线程是否继续工作。当 keep_workingTrue 时,线程会一直工作;当它为 False 时,线程会停止工作。你可以通过设置 keep_working = False 来停止所有线程。

对比分析

  1. threading.Thread类的方法

使用threading.Thread类创建线程的方法简单直观,可以直接实例化一个Thread对象,然后调用其start()方法启动线程。这种方法的优势在于代码简洁易懂,易于调试和维护。但是,它的缺点也很明显:

  • 内存占用:由于每个线程都需要分配一定的内存空间,当需要创建大量线程时,内存占用会成为一个严重的问题。尤其是在资源受限的环境中,可能导致程序崩溃或性能下降。
  • 线程管理:Thread类没有提供线程池的功能,因此需要手动管理线程的创建、启动、销毁等操作。这在处理大量任务时会增加编程复杂度和出错概率。
import threading

def worker(arg):
    # 执行任务
    pass

for i in range(10):
    t = threading.Thread(target=worker, args=(i,))
    t.start()
  1. concurrent.futures.ThreadPoolExecutor类的方法

ThreadPoolExecutor类提供了一个线程池,可以有效地管理和复用线程资源。相比于Thread类,它具有以下优势:

  • 高效率:线程池可以复用已创建的线程,避免了频繁地创建和销毁线程带来的性能损耗。
  • 易于管理:线程池内部自动管理线程的生命周期,简化了编程复杂度。
  • 异步执行:可以通过submit()方法提交任务,并返回一个Future对象,方便后续对任务状态的查询和结果的获取。

然而,ThreadPoolExecutor也有一些局限性:

  • 资源限制:线程池的大小是固定的,如果任务数量远远超过线程池的大小,可能会导致任务积压和延迟。
  • 灵活性较低:相比于直接使用Thread类,ThreadPoolExecutor的使用相对复杂,需要熟悉其API和工作原理。
from concurrent.futures import ThreadPoolExecutor

def worker(arg):
    # 执行任务
    pass

with ThreadPoolExecutor(max_workers=5) as executor:
    futures = [executor.submit(worker, i) for i in range(10)]
  1. queue.Queue类的方法

Queue类提供了一种线程安全的队列实现,可以实现生产者-消费者模型。这种模型适用于需要处理大量任务的场景,具有以下优势:

  • 解耦:生产者和消费者之间的通信通过队列进行,降低了它们之间的耦合度。
  • 可扩展性:可以根据需求动态调整生产者和消费者的数量,以适应不同的负载情况。
  • 负载均衡:多个消费者可以同时从队列中获取任务,实现负载均衡。

然而,Queue类的使用也有一些局限性:

  • 编程复杂度:实现生产者-消费者模型需要额外的编程工作,相对于前两种方法更加复杂。
  • 资源竞争:多个线程同时访问队列可能导致资源竞争,影响性能。
import queue
import threading

def producer(q):
    for i in range(10):
        q.put(i)

def consumer(q):
    while True:
        task = q.get()
        if task is None:
            break
        # 执行任务
        pass

q = queue.Queue()
producer_thread = threading.Thread(target=producer, args=(q,))
consumer_thread = threading.Thread(target=consumer, args=(q,))
producer_thread.start()
consumer_thread.start()
producer_thread.join()
q.put(None)
consumer_thread.join()

综上所述,在实际使用中,我们可以根据具体需求选择合适的方法。对于简单的多线程任务,可以使用threading.Thread类;对于需要处理大量任务的场景,可以使用concurrent.futures.ThreadPoolExecutor类或queue.Queue类实现生产者-消费者模型。

拓展

案例3:

案例1中,新增要求3:设置条件,如果打印超过i次,则关闭对应的线程,,且为了便于区分,,输出关闭了线程1,关闭了线程2 …类似的提示语句

1. 使用threading.Thread

import threading
import time

# 定义一个全局变量,用于记录每个线程打印的次数
count_dict = {}

def print_and_sleep(i, thread_name):
    count = 0
    while count < i:
        print(f'{thread_name}: {i}')
        time.sleep(i)
        count += 1
        count_dict[thread_name] = count
        if count >= i:
            print(f'关闭了{thread_name}')

l1 = [1, 2, 3, 4, 5, 6]

for index, i in enumerate(l1):
    thread_name = f'线程{index+1}'
    t = threading.Thread(target=print_and_sleep, args=(i, thread_name))
    t.start()

2. 使用concurrent.futures.ThreadPoolExecutor

在Python的concurrent.futures模块中,ThreadPoolExecutor类可以用于并行执行线程。下面是使用ThreadPoolExecutor的代码示例:

import concurrent.futures
import time

def print_and_sleep(i, thread_name):
    count = 0
    while count < i:
        print(f'{thread_name}: {i}')
        time.sleep(i)
        count += 1
        if count >= i:
            print(f'关闭了{thread_name}')

with concurrent.futures.ThreadPoolExecutor() as executor:
    l1 = [1, 2, 3, 4, 5, 6]
    futures = [executor.submit(print_and_sleep, i, f'线程{i}') for i in l1]
    for future in concurrent.futures.as_completed(futures):
        print(f'已完成: {future}')

在这个示例中,我们使用with语句创建了一个ThreadPoolExecutor实例,然后使用submit方法提交了多个任务到线程池中。as_completed方法用于等待所有任务完成。

3. 使用queue.Queue

Python的queue模块提供了一个线程安全的队列实现,可以用于多线程之间的通信和同步。以下是一个使用queue.Queue的示例:

import threading
import queue
import time

def print_and_sleep(q, i, thread_name):
    count = 0
    while count < i:
        item = q.get()  # 从队列中获取一个任务
        if item is None:  # 如果队列中没有任务,则退出循环
            break
        print(f'{thread_name}: {item}')
        time.sleep(i)  # 休眠指定的时间
        count += 1
        q.task_done()  # 通知队列任务已完成
        if count >= i:
            print(f'关闭了{thread_name}')
    q.put(None)  # 将一个None对象放入队列,表示线程结束

q = queue.Queue()  # 创建一个队列对象
l1 = [1, 2, 3, 4, 5, 6]
for i in l1:
    q.put(i)  # 将任务放入队列中

threads = []  # 存储所有线程的列表
for index in range(len(l1)):
    thread_name = f'线程{index+1}'
    t = threading.Thread(target=print_and_sleep, args=(q, l1[index], thread_name))
    threads.append(t)  # 将线程添加到列表中
    t.start()  # 启动线程

q.join()  # 等待所有任务完成
for t in threads:  # 等待所有线程结束
    t.join()

在这个示例中,我们创建了一个queue.Queue对象,并向其中添加了多个任务。然后,我们为每个任务启动了一个线程,每个线程从队列中获取任务并执行。线程通过调用task_done方法通知队列任务已完成,然后通过将一个None对象放入队列来标记线程结束。最后,我们使用join方法等待所有任务和线程完成。

你可能感兴趣的:(Python学习私人笔记,多线程编程私人笔记,python,开发语言,多线程)