Python 多进程 multiprocessing 使用示例

本文详细介绍了Python的多进程编程,包括从单线程、多线程的基础知识,深入到多进程的使用,如线程同步、事件、队列、管道、共享内存和Manager对象。重点讲解了multiprocessing模块,涵盖了Process、Pool等关键类的使用,并通过实例演示了如何创建进程、管理进程池,以及如何通过Queue和Pipe进行进程间通信。同时,文章强调了多进程在处理CPU密集型任务时的优势和在数据共享方面的注意事项。
摘要由CSDN通过智能技术生成

multiprocessing 文档:https://docs.python.org/zh-cn/3.10/library/multiprocessing.html
Process、Lock、Semaphore、Queue、Pipe、Pool:https://cuiqingcai.com/3335.html

把一个多线程改成多进程,主要有下面几种方法:

  1. subprocess
  2. signal
  3. threading
  4. multiprocessing
  5. concurrent.futures 标准库它提供了 ThreadPoolExecutor 和 ProcessPoolExecutor 两个类,实现了对 threading 和 multiprocessing 的更高级的抽象,对编写 线程池/进程池 提供了直接的支持。 

使用示例代码:

# -*- coding: utf-8 -*-

import redis
from redis import WatchError
from concurrent.futures import ProcessPoolExecutor

r = redis.Redis(host='127.0.0.1', port=6379)

# 减库存函数, 循环直到减库存完成
# 库存充足, 减库存成功, 返回True
# 库存不足, 减库存失败, 返回False


def reduce_stock():

    # python中redis事务是通过pipeline的封装实现的
    with r.pipeline() as pipe:
        while True:
            try:
                # watch库存键, multi后如果该key被其他客户端改变, 
                # 事务操作会抛出WatchError异常
                pipe.watch('stock:count')
                count = int(pipe.get('stock:count'))
                if count > 0:  # 有库存
                    # 事务开始
                    pipe.multi()
                    pipe.decr('stock:count')
                    # 把命令推送过去
                    # execute返回命令执行结果列表, 这里只有一个decr返回当前值
                    print(pipe.execute()[0])
                    return True
                else:
                    return False
            except WatchError as ex:
                # 打印WatchError异常, 观察被watch锁住的情况
                print(ex)
                pipe.unwatch()


def worker():
    while True:
        # 没有库存就退出
        if not reduce_stock():
            break


if __name__ == "__main__":
    # 设置库存为100
    r.set("stock:count", 100)

    # 多进程模拟多个客户端提交
    with ProcessPoolExecutor() as pool:
        for _ in range(10):
            pool.submit(worker)

1、Python 单线程 和 多线程

Python 单线程

# -*- coding:utf-8 -*-

import time
import datetime


def music(argv):
    for i in range(2):
        print("听音乐  %s. %s" % (argv, datetime.datetime.now()))
        time.sleep(1)


def movie(argv):
    for i in range(2):
        print("看电影  {}. {}".format(argv, datetime.datetime.now()))
        time.sleep(5)


if __name__ == '__main__':
    music('trouble is a friend')
    movie('变形金刚')
    print(f"all over {datetime.datetime.now()}")

Python 多线程

Python 中使用线程有两种方式:

  • 通过使用 start_new_thread 函数
  • 用类来包装线程对象

start_new_thread() 函数来产生新线程

函数式:调用 _thread 模块中的 start_new_thread() 函数来产生新线程

语法如下:

  • _thread.start_new_thread ( function, args[, kwargs] )

参数说明:

  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个tuple类型。
  • kwargs - 可选参数。

使用示例:

import time
import _thread


# 为线程定义一个函数
def print_time(thread_name, delay):
    count = 0
    while count < 5:
        time.sleep(delay)
        count += 1
        print("%s: %s" % (thread_name, time.ctime(time.time())))


if __name__ == '__main__':
    try:
        # 创建两个线程
        _thread.start_new_thread(print_time, ("Thread-1", 2,))
        _thread.start_new_thread(print_time, ("Thread-2", 4,))
    except BaseException as be:
        print(f"Error: 无法启动线程 ---> {be}")

    while True:
        pass

通过 继承 threading.Thread 来实现多线程

使用 Threading 模块创建线程,直接从 threading.Thread 继承,然后重写 run 方法:

import threading
import time

exitFlag = 0


class myThread(threading.Thread):
    def __init__(self, threadID, name, delay):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.delay = delay

    def run(self):
        print("开始线程:" + self.name)
        print_time(self.name, self.delay, 5)
        print("退出线程:" + self.name)


def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threadName.exit()
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("退出主线程")

_thread 和 threading 模块( 强烈建议直接使用 threading )。python 提供了两个模块来实现多线程。 thread 有一些缺点,在 threading 得到了弥补。

setDaemon(True) 设置 精灵进程  

setDaemon(True) 将线程声明为守护线程,必须在 start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句 print("父进程退出") 后,没有等待子线程,直接就退出了,同时子线程也一同结束。

# -*- coding: utf-8 -*-

import time
import threading
import datetime


def music(music_name):
    for i in range(2):
        print(f"[{datetime.datetime.now().replace(microsecond=0)}] 听音乐 {music_name}")
        time.sleep(1)


def movie(movie_name):
    for i in range(2):
        print(f"[{datetime.datetime.now().replace(microsecond=0)}] 看电影 {movie_name}")
        time.sleep(5)


tid_list = [
    threading.Thread(target=music, args=('trouble is a friend',)),
    threading.Thread(target=movie, args=('变形金刚',))
]


if __name__ == '__main__':
    for tid in tid_list:
        tid.setDaemon(True)
        tid.start()
    print("父进程退出")
    pass

join() 方法,等待线程终止

join() 的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。注意:上面程序中 join() 方法的位置是在 for 循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。

# -*- coding: utf-8 -*-

import time
import threading
import datetime


def music(music_name):
    for i in range(2):
        print(f"[{datetime.datetime.now().replace(microsecond=0)}] 听音乐 {music_name}")
        time.sleep(1)


def movie(movie_name):
    for i in range(2):
        print(f"[{datetime.datetime.now().replace(microsecond=0)}] 看电影 {movie_name}")
        time.sleep(5)


tid_list = [
    threading.Thread(target=music, args=('trouble is a friend',)),
    threading.Thread(target=movie, args=('变形金刚',))
]


if __name__ == '__main__':
    for tid in tid_list:
        tid.start()
    for tid in tid_list:
        tid.join()
    print("父进程退出")

threading.Thread 方法说明

t.start()       激活线程,   
t.getName()     获取线程的名称   
t.setName()     设置线程的名称   
t.name          获取或设置线程的名称   
t.is_alive()    判断线程是否为激活状态   
t.isAlive()     判断线程是否为激活状态   
t.setDaemon()   设置为后台线程或前台线程(默认:False);
                通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之后才可以使用。
                如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;
                如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止   
t.isDaemon()    判断是否为守护线程   
t.ident         获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。   
t.join()        逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义   
t.run()         线程被cpu调度后自动执行线程对象的run方法

2、线程同步

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

        多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。考虑这样一种情况:一个列表里所有元素都是 0,线程 "set" 从后向前把所有元素改成1,而线程 "print" 负责从前往后读取列表并打印。那么,可能线程 "set" 开始改的时候,线程 "print" 便来打印列表了,输出就成了一半 0 一半 1,这就是 数据的不同步。
为了避免这种情况,引入了
的概念。锁有两种状态:锁定未锁定
每当一个线程比如 "set" 要访问共享数据时,必须先获得锁定;如果已经有别的线程比如 "print" 获得锁定了,那么就让线程 "set" 暂停,也就是同步阻塞;等到线程 "print" 访问完毕,释放锁以后,再让线程 "set" 继续。经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

线程锁 threading.RLockthreading.Lock

使用 Thread对象Lock Rlock 可以实现简单的线程同步,这两个对象都有 acquire方法 release方法。对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和release 方法之间。

threading.RLockthreading.Lock 的区别:

  • RLock 允许在同一线程中被多次 acquire。而 Lock 却不允许这种情况。使用 RLock 时 acquire 和 release 必须成对出现,即调用了 n 次 acquire,必须调用 n 次的release 才能真正释放所占用的琐。
# -*- coding: utf-8 -*-

import threading

lock = threading.Lock()    # Lock对象
rLock = threading.RLock()  # RLock对象 


def main_1():
    lock.acquire()
    lock.acquire()  # 产生了死琐。
    lock.release()
    lock.release()


def main_2():    
    rLock.acquire()
    rLock.acquire()  # 在同一线程内,程序不会堵塞。 
    rLock.release()
    rLock.release()

示例:

# -*- coding: utf-8 -*-

import time
import datetime
import threading

# 定义一个 "线程锁"
threadLock = threading.Lock()

count = 20


class MyThread(threading.Thread):
    def __init__(self, name, delay_second):
        threading.Thread.__init__(self)
        self.name = name
        self.delay_second = delay_second

    def run(self):
        print("Starting " + self.name)
        result = print_time()
        while result > 0:
            print(f"[{datetime.datetime.now().replace(microsecond=0)}] {self.name} ---> {result}")
            result = print_time()
            time.sleep(self.delay_second)


def print_time():
    # 获得锁,成功获得锁定后返回 True
    # 可选的 timeout 参数不填时将一直阻塞直到获得锁定
    # 否则超时后将返回 False
    threadLock.acquire()
    global count
    count -= 1
    # 释放锁
    threadLock.release()
    return count


if __name__ == '__main__':
    tid_list = [
        MyThread("thread_1", 1),
        MyThread("thread_2", 1)
    ]

    for tid in tid_list:
        tid.start()
    for tid in tid_list:
        tid.join()
    print("主线程退出")
    pass

示例:

import threading
import
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值