Python教程(十六):进程、线程、线程锁

专栏列表


在这里插入图片描述

正文开始如果觉得文章对您有帮助,请帮我三连+订阅,谢谢💖💖💖


引言

在现代软件开发中,充分利用多核处理器的能力,提高应用程序的性能和响应性,是每个开发者都需要考虑的问题。Python 作为一门高级编程语言,提供了多种并发编程的解决方案,包括多进程、多线程和线程锁

进程与线程基础

进程(Process)

进程是操作系统进行资源分配和调度的一个独立单位,每个进程都有自己的内存空间和系统资源。我们可以把进程想象成手机中的一个APP,每个进程(APP)都有自己独立的内存空间,数据独立不互扰

线程(Thread)

线程是进程中的为完成一些事情而独立出的分支,是被系统独立调度和分派的基本单位,一个进程中至少有一个线程,多个线程间可以共享同一个进程所拥有的全部资源。例如一个APP拥有(发送网络线程,执行脚本线程,渲染界面线程)

Python 多进程

Python 的 multiprocessing 模块允许你创建进程,利用多核处理器的优势。由于 Python 的全局解释器锁(GIL),多进程是实现 CPU 密集型任务并行化的首选。

  • 调用用start()方法启动进程
  • join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
  • os.getpid() 可以获取系统的进程Id, os.getppid() 可以获取父进程的 进程ID
# 基本使用
from multiprocessing import Process
import os
def worker(name , addr):
    print(f'Worker: {name } {addr}, 父进程pid {os.getppid()}')

if __name__ == '__main__':
    print(f'系统pid {os.getpid()}')
    p = Process(target=worker, args=('新进程','未知'))
    p.start()
    p.join()

进程池

如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
    print(f'运行任务 {name} {os.getpid()}')
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('PID %s.' % os.getpid())
    p = Pool(4) # 开启4个进程池
    for i in range(5):
        p.apply_async(long_time_task, args=(i,)) # 添加 5 个同步任务 , 就会出现任务等待情况
    print('等到所有子进程执行结束...')
    p.close() # 调用close()之后就不能继续添加新的Process了。
    p.join()
    print('所有进程执行结束')

在这里插入图片描述

子进程

很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。



import subprocess

print('$ ping www.baidu.com')
r = subprocess.call(['ping', 'www.baidu.com'])
print('Exit code:', r)


在这里插入图片描述

进程间通信

Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:


from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
    print('Process to write: %s' % os.getpid())
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    print('Process to read: %s' % os.getpid())
    while True:
        value = q.get(True)
        print('Get %s from queue.' % value)

if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 启动子进程pr,读取:
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    pr.terminate()

在这里插入图片描述

Python 线程

Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。然而,由于 GIL 的存在,多线程在 CPU 密集型任务上的优势并不明显,但在 I/O 密集型任务上,多线程可以显著提高性能。

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

import threading

def print_numbers():
    for i in range(1, 6):
        print(f'当前线程{threading.current_thread().name} , 输出值 {i}')

thread = threading.Thread(target=print_numbers ,name='ziyu')
thread.start()
thread.join()
print(f'默认主线程 {threading.current_thread().name} 执行完毕')
  • Python的threading模块有个current_thread()函数,它永远返回当前线程的实例
  • 主线程实例的名字叫 MainThread 子线程不起名字Python就自动给线程命名为Thread-1,Thread-2……
    在这里插入图片描述

多线程示例

import threading

def print_numbers():
    for i in range(1, 6):
        print(f'Thread {threading.current_thread().name}: {i}')

threads = []
for i in range(2):
    t = threading.Thread(target=print_numbers)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

锁(Lock)

多线程和多进程最大的不同在于,多进程中内存空间是相互独立的,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,
多线程中,所有变量都由所有线程共享,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,容易出现数据错误

在 Python 中,如果没有使用锁(例如 threading.Lock),多个线程同时访问和修改共享数据时可能会遇到竞态条件(Race Condition),导致数据不一致或程序行为异常。

请注意,由于 Python 的全局解释器锁(Global Interpreter Lock),在 CPython 解释器中,这种简单的自增操作实际上可能不会遇到竞态条件,因为 GIL 会确保在任何给定时间只有一个线程执行 Python 字节码。

# 共享计数器
counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1

# 创建两个线程
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)

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

# 等待线程完成
thread1.join()
thread2.join()

print(f"期待计数: 200000")
print(f"实际计数: {counter}")

为了避免竞态条件,我们可以使用 threading.Lock 来同步对共享资源的访问:

import threading

# 共享计数器
counter = 0
# 创建一个锁
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        # 获取锁
        lock.acquire()
        try:
            counter += 1
        finally:
            # 释放锁
            lock.release()

# 创建两个线程
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)

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

# 等待线程完成
thread1.join()
thread2.join()

print(f"期待计数: 200000")
print(f"实际计数: {counter}")

多进程 vs 多线程

  • 多进程:适合 CPU 密集型任务,可以绕过 GIL,利用多核处理器。
  • 多线程:适合 I/O 密集型任务,由于 GIL 的存在,CPU 密集型任务并行效果不佳。
  • 21
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子羽bro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值