Python多线程编程详解

    Python多线程编程详解_第1张图片

 


概要

进程(process)指的是正在运行的程序的实例,当我们执行某个程序时,进程就被操作系统创建了。而线程(thread)则包含于进程之中,是操作系统能够进行运算调度的最小单元,多个线程可以同处一个进程中,且同时处理不同的任务。一条进程中可以并发多个线程,而同一条线程将共享该进程中的全部系统资源。

每个进程都有自己独立的地址空间、内存和数据栈,因此进程之间通讯不方便,所以需使用用进程间通讯(InterProcess Communication, IPC)。而同一个进程中的线程共享资源,因此线程间通讯非常方便,只需注意数据同步与互斥的问题。

Python支持多线程编程和多进程编程。在本教程中,我们将学习有关Python多线程编程的基础知识、线程同步、线程池以及如何使用多进程。


1. 多线程基础

线程是进程内的执行单元,每个进程都至少有一个线程。Python标准库提供了thread模块和threading模块来支持线程编程。在Python3中,thread模块已经改名为_thread,并在_thread基础上开发了更为强大的threading模块,因此我们可以使用threading.Thread类创建线程对象,并通过start()方法启动线程。

import threading



def worker():

    print("I am a thread.")



t = threading.Thread(target=worker)

t.start()

在上面的代码中,我们创建了一个名为worker的函数,它是线程的工作内容。我们使用threading.Thread类创建了一个线程对象t,并将worker作为参数传递给了Thread构造函数。然后,我们调用t.start()方法来启动线程。当我们运行这段代码时,会看到以下输出:

I am a thread.

另外,我们也可以通过继承Thread类并重写run方法来实现线程的创建

import threading



class MyThread(threading.Thread):

    def __init__(self, n):

        self.n = n

        super().__init__() # 调用父类函数初始化线程

    def run(self):

        print('线程:', self.n)

for i in range(1,3):

    t = MyThread(i)

    t.start()

结果应该输出打印出:

线程1

线程2

2. 多线程同步

当多个线程同时访问共享资源时,可能会发生竞态条件。为避免这种情况,我们需要使用线程同步机制。Python提供了锁(Lock)、信号量(Semaphore)、事件(Event)等机制来实现线程同步。

下面是一个使用锁来确保线程同步的示例:

import threading



lock = threading.Lock()



count = 0



def worker():

    global count

    lock.acquire()

    try:

        count += 1

    finally:

        lock.release()



threads = []

for i in range(10):

    t = threading.Thread(target=worker)

    threads.append(t)



for t in threads:

    t.start()



for t in threads:

    t.join()



print(count)

在上面的代码中,我们定义了一个全局变量count和一个锁对象lockworker函数是每个线程的工作内容,它通过lock来确保count的修改是原子性的。我们创建了10个线程并启动它们,等待所有线程执行完毕后输出count的值。当我们运行这段代码时,会看到以下输出:

10

如果把锁去掉后,count的值会是几呢?动手试试吧!会有意向不到的结果哦~

3. 线程池

线程池可以用来提高代码的性能,在需要大量线程处理任务时,可以使用线程池来减少线程的创建和销毁次数,提高线程的复用率。

Python标准库提供了concurrent.futures模块来支持线程池编程。我们可以使用ThreadPoolExecutor类来创建和管理线程池。

下面是一个使用线程池来处理任务的示例:

from concurrent.futures import ThreadPoolExecutor



def worker(num):

    return num * num



executor = ThreadPoolExecutor(max_workers=4)



results = []

for i in range(10):

    result = executor.submit(worker, i)

    results.append(result)



for result in results:

    print(result.result())

在上面的代码中,我们定义了一个worker函数用来处理任务。我们创建了一个ThreadPoolExecutor对象executor,并将最大工作线程数设置为4。然后,我们使用submit方法提交10个任务给executor,并将结果保存在results列表中。最后,我们遍历results列表并输出每个任务的结果。

4. 多进程编程

进程是计算机中程序执行的基本单位,而线程则是进程中执行代码的单位。多进程编程就是利用多个独立运行的进程来完成任务。相比单进程,多进程有以下优点:

• 提高了程序的并发性能。

• 可以更好地利用多核CPU

• 能够处理更大的数据集。

在进行多进程编程时,需要注意进程之间的通信和同步问题。

5. Python实现多进程编程

Python标准库中的multiprocessing模块提供了实现多进程编程的工具。其中常用的方法包括:

• Process:创建一个新进程。

• Pool:创建一组进程池。

• Queue:进程之间的消息队列。

下面是一个简单的示例,展示如何使用multiprocessing模块创建子进程:

import multiprocessing



def worker():

    """子进程要执行的任务"""

    print('子进程正在运行')



if __name__ == '__main__':

    p = multiprocessing.Process(target=worker)

    p.start()

    print('主进程已经结束')

在这个例子中,我们创建一个新的进程,并将其target属性指向worker函数。然后使用start方法启动该进程。注意到在Windows系统中,需要将启动进程的代码放在if __name__ == '__main__':语句内,以避免出现一些问题。

5.1 Python进程池

import multiprocessing



def worker(num):

    """子进程要执行的任务"""

    print(f'子进程{num}正在运行')



if __name__ == '__main__':

    with multiprocessing.Pool(processes=4) as pool:

        results = pool.map(worker, range(4))

        print(results)

在这个例子中,我们使用了进程池来管理多个子进程。首先创建一个Pool对象,其中processes参数指定了进程池的大小。然后使用map方法提交任务,该方法会自动分配任务给空闲的子进程并返回结果。最后打印输出结果。

5.2 在进程之间进行通信

import multiprocessing



def writer(q):

    """写入数据"""

    for i in range(10):

        q.put(i)



def reader(q):

    """读取数据"""

    while True:

        item = q.get()

        if item is None:

            break

        print(item)



if __name__ == '__main__':

    q = multiprocessing.Queue()



    # 启动子进程

    p1 = multiprocessing.Process(target=writer, args=(q,))

    p2 = multiprocessing.Process(target=reader, args=(q,))

    p1.start()

    p2.start()



    # 等待子进程结束

    p1.join()

    q.put(None)

    p2.join()

在这个例子中,我们创建了一个消息队列,并通过Queue对象将其传递给两个子进程。一个子进程负责将数据写入队列中,而另一个子进程则从队列中读取数据并输出到屏幕上。注意到在程序末尾我们向队列中添加了None元素,以便通知读取数据的子进程结束循环。

6.多进程计算圆周率

下面是使用Python多进程和蒙特卡罗(Monte Carlo)方法估计圆周率的示例代码:

import random

from multiprocessing import Pool



def estimate_pi(n):

    num_points_inside_circle = 0

    for _ in range(n):

        x = random.uniform(-1, 1)

        y = random.uniform(-1, 1)

        if x**2 + y**2 <= 1:

            num_points_inside_circle += 1

    return 4 * num_points_inside_circle / n



if __name__ == '__main__':

    num_processes = 4

    num_samples = int(1e8)

    with Pool(num_processes) as p:

        results = p.map(estimate_pi, [int(num_samples/num_processes)] * num_processes)

    print(sum(results)/num_processes)

该代码使用random模块中的uniform函数生成在$[-1, 1]$范围内均匀分布的随机数,并统计落入以原点为圆心,半径为1的圆中的点数。最后,将所有子进程的结果相加并除以进程数以得到圆周率的估计值。我算出的结果是3.1416664800000005, 你们呢?

其中,Pool(num_processes) 创建一个具有num_processes个进程的进程池,p.map(estimate_pi, [int(num_samples/num_processes)] * num_processes) 在每个进程上调用estimate_pi函数,并将输入参数设为所需的样本数量的四分之一,然后返回其结果。

7.总结

Python多线程与进程编程中,我们不仅要理解和掌握的关于threading 模块和multiprocessing模块如何使用,更要掌握的是线程和进程之间的区别和联系以及线程通信、进程通信的方式。

欢迎点赞收藏转发,感谢!

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