📜  讨论Python的并发性(1)

📅  最后修改于: 2023-12-03 15:28:06.542000             🧑  作者: Mango

讨论Python的并发性

Python 语言支持多种并发编程方式,其中比较常用的方式有多线程和多进程。

多线程

在 Python 中,可以通过 threading 模块来进行多线程编程。使用多线程可以提高程序的并发性,同时也能够更充分地利用计算机的多核 CPU。

创建线程

要创建一个新的线程,可以通过 threading.Thread 类来实现。例如:

import threading

def print_thread_info():
    print("thread info:", threading.current_thread())

thread = threading.Thread(target=print_thread_info)
thread.start()

在上面的代码中,我们创建了一个新的线程,并指定它要执行的函数是 print_thread_info,然后调用 start 方法来启动线程。在 print_thread_info 函数中,我们通过 threading.current_thread() 函数获取当前线程的信息。

线程同步

多线程编程涉及到共享数据的问题,为了避免出现数据竞争和死锁等问题,需要进行线程同步。常用的同步机制包括:锁、条件变量和信号量等。

例如,可以使用 threading.Lock 类来实现对共享资源的互斥访问:

import threading

total = 1000
lock = threading.Lock()

def withdraw(money):
    global total
    lock.acquire()
    total -= money
    lock.release()

t1 = threading.Thread(target=withdraw, args=(200,))
t2 = threading.Thread(target=withdraw, args=(300,))
t1.start()
t2.start()
t1.join()
t2.join()
print("final total:", total)

在上面的例子中,我们定义了一个全局变量 total,表示银行账户的余额。同时,创建了两个线程,分别调用 withdraw 函数来取款,每次取款之后,需要首先获取锁,然后对 total 进行更新,最后释放锁。这样就可以保证在任意时刻只有一个线程在对 total 进行访问。

GIL

Python 的全局解释器锁 (Global Interpreter Lock,简称 GIL) 是 Python 运行时的一个限制,它保证了任何时候只有一个线程能够执行 Python 字节码,从而导致了 Python 不能真正地利用多核处理器。不过,如果我们在 Python 中调用了一些 C 扩展模块,例如 NumPy 或 Pandas,那么 GIL 就不会起作用,因为这些模块使用的是 C 语言编写的本地代码。

多进程

与多线程不同,多进程能够真正地利用多核 CPU,提高程序的计算能力。在 Python 中,可以通过 multiprocessing 模块来进行多进程编程。

创建进程

要创建一个新的进程,可以通过 multiprocessing.Process 类来实现。例如:

import multiprocessing

def print_process_info():
    print("process info:", multiprocessing.current_process())

process = multiprocessing.Process(target=print_process_info)
process.start()

在上面的代码中,我们创建了一个新的进程,并指定它要执行的函数是 print_process_info,然后调用 start 方法来启动进程。在 print_process_info 函数中,我们通过 multiprocessing.current_process() 函数获取当前进程的信息。

进程池

如果需要多次创建进程,或者需要对多个数据进行并行处理,那么可以使用进程池来避免频繁地创建和销毁进程的开销。

例如,可以使用 multiprocessing.Pool 类来创建一个进程池,并使用它来处理多个数据:

import multiprocessing

def square(x):
    return x * x

if __name__ == '__main__':
    with multiprocessing.Pool(processes=4) as pool:
        results = pool.map(square, range(10))
    print(results)

在上面的例子中,我们定义了一个函数 square,用来计算一个数的平方。然后,使用进程池来对多个数据进行并行计算,最后输出结果。

进程之间的通信

多个进程之间可以通过队列、管道和共享内存等方式来进行通信。

例如,可以使用 multiprocessing.Queue 来实现进程之间的消息传递:

import multiprocessing

def sender(queue):
    for i in range(3):
        print('Sent:', i)
        queue.put(i)

def receiver(queue):
    while True:
        data = queue.get()
        print('Received:', data)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    p1 = multiprocessing.Process(target=sender, args=(queue,))
    p2 = multiprocessing.Process(target=receiver, args=(queue,))
    p1.start()
    p2.start()
    p1.join()
    p2.terminate()

在上面的代码中,我们创建了一个队列,并创建了两个进程,一个用来发送消息,另一个用来接收消息。在发送进程中,通过 queue.put 函数来向队列中添加数据,而在接收进程中,通过 queue.get 函数来从队列中获取数据。

总结

Python 语言支持多种并发编程方式,包括多线程和多进程。在多线程编程时需要注意线程同步的问题,而在多进程编程中则需要使用进程池和进程之间的通信机制来提高程序的计算能力。同时,要注意 GIL 对多线程的影响,以及 Python C 扩展模块的使用。