速战速决 Python - python 标准库: 多线程和线程同步,异步编程,多进程,多进程数据共享,多进程数据通信

速战速决 Python https://github.com/webabcd/PythonSample
作者 webabcd

速战速决 Python - python 标准库: 多线程和线程同步,异步编程,多进程,多进程数据共享,多进程数据通信

示例如下:

standardLib/thread.py

# 通过 import threading 实现多线程(注:一个进程同一个时刻只有一个线程在执行,要想利用多核 cpu 则需要用多进程)

import threading
import time
from threading import Timer

# 继承 threading.Thread
class MyThread(threading.Thread):

    def __init__(self, threadName):
        threading.Thread.__init__(self)
        self.name = threadName

    # 需要在新开线程中执行的逻辑
    def run(self):
        myPrint(self.name + " 开始")

        temp = 0
        start = time.perf_counter() # 获取设备开机到现在经过的秒数
        while True:
            if temp < 10:
                myPrint(self.name + " 运行中")
                time.sleep(0.2) # 阻塞 0.2 秒
                temp += 1
            else:
                break

        end = time.perf_counter()
        myPrint(f"{self.name} 运行了 {end-start} {start}秒,退出")

# 通过 threading.Lock() 实现线程同步
lock = threading.Lock()
def myPrint(message):
    # 获取锁
    lock.acquire()
    print(message) # 自己可以测试一下,不用锁的话打印会比较乱,用了锁就不会了
    # 释放锁
    lock.release()


# 创建新线程
thread1 = MyThread("thread_1")
thread2 = MyThread("thread_2")
thread3 = MyThread("thread_3")

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

# 在当前线程等待新开线程执行完毕
thread1.join()
thread2.join()
thread3.join()



def after():
    print('after')
# 通过 timer 实现在指定时间后执行指定函数的功能
timer = Timer(1, after)
timer.start()
timer.join()

standardLib/async.py

# 本例用于演示如何通过 async/await 实现异步编程
# 协程相当于轻量级的线程(注:一个进程同一个时刻只有一个协程在执行,要想利用多核 cpu 则需要用多进程)

import asyncio

# 通过 async 修饰的函数支持异步执行
async def func1():
    print('coroutine1')
async def func2():
    print('coroutine2')
# 构造需要并行执行的异步函数列表
task = [func1(), func2()]
# 创建事件循环
loop = asyncio.new_event_loop()
# 阻塞,直至所有异步函数执行完成
loop.run_until_complete(asyncio.wait(task))
# 关闭事件循环
loop.close()



# async 标记的函数就是一个 coroutine,你可以 await 它
async def func3(name, delay):
    await asyncio.sleep(delay)
    print(name)
async def sample1():
    # await 一个 async 函数,直到它执行完毕
    await func3("i", 1) # 注:这里直接执行 func3("i", 1) 是不可以的,如果需要执行一个 async 函数且不需要等待,则可以 asyncio.create_task(func3("i", 1))
    await func3("j", 1)
    await func3("k", 1)

    # 并行执行多个异步函数
    await asyncio.gather(
        func3("a", 3),
        func3("b", 2),
        func3("c", 1),
    )
# 阻塞,直至指定的异步函数执行完成(不需要手写事件循环)
asyncio.run(sample1())



async def sample2():
    # 创建一个异步任务并立刻并行执行(下面的 task1,task2,task3 会并行执行)
    task1 = asyncio.create_task(func3("x", 1))
    task2 = asyncio.create_task(func3("y", 1))
    task3 = asyncio.create_task(func3("z", 1))
    # 阻塞,直至 task1,task2,task3 执行完成
    await task1
    await task2
    await task3
# 阻塞,直至指定的异步函数执行完成
asyncio.run(sample2())

standardLib/process.py

# 本例用于演示 python 的多进程
# 
# 在 cpython 解释器中,GIL 是一把互斥锁,用来保证进程中同一个时刻只有一个线程在执行
# 所以你即使在一个进程中开了多线程,他们也无法并行执行
#
# coroutine(async/awawit) 要比 thread 简单,并且一个进程中同一个时刻只有一个线程或协程在执行,他们的运行效率没区别,所以尽量都用 coroutine
# 一个进程只能在一个核上运行,如果要充分利用多核 cpu 则需要用多进程
#
# io(网络请求,硬盘存储等)密集型建议用 coroutine,因为它的 cpu 占用率不高,一个核也足够支撑多个 io 任务的同时高效运行
# 计算密集型建议用多进程,否则会因为单核 cpu 占用极高,而阻塞其他线程或协程的运行

import multiprocessing
import time
import os

def func1(i, lock):
    # 获取锁
    lock.acquire()
    print(i, os.getpid())
    # 释放锁
    lock.release()
    time.sleep(1)

# 通过 Process() 开启多进程
def sample1():
    now = time.time()
    l=[]

    # 通过 multiprocessing.Lock() 实现进程同步
    # 需要把这个 lock 传给每个进程,然后在每个进程里 acquire/release
    lock = multiprocessing.Lock()
    for i in range(10):
        p = multiprocessing.Process(target=func1, args=(i,lock))
        p.daemon = True # 是否是守护进程
        p.start()
        l.append(p)

    for p in l:
        p.join() # 阻塞并等待指定的进程执行完毕
        # 强制杀死指定的进程
        # p.kill()

    print('sample1 ok', time.time() - now)



def func2(i, lock):
    # 获取锁
    lock.acquire()
    print(i, os.getpid())
    # 释放锁
    lock.release()
    time.sleep(1)

# 通过进程池开启多进程
def sample2():
    now = time.time()

    # 通过 multiprocessing.Manager().Lock() 实现进程同步
    # 注:在 multiprocessing.Pool() 中不能用 multiprocessing.Lock()
    lock = multiprocessing.Manager().Lock()
    # 注:在 multiprocessing.Pool() 中创建的进程只能是用户进程,而不能创建为守护进程
    pool = multiprocessing.Pool(processes=2) # 池中最大进程数
    for i in range(10):
        pool.apply_async(func=func2, args=(i,lock)) # 从池中申请进程,如果暂时无可用的进程就排队等待

    pool.close() # 关闭进程池,关闭后就不能再申请了
    pool.join() # 阻塞并等待池中进程全部执行完毕

    print('sample2 ok', time.time() - now)


if __name__ == '__main__':
    sample1()
    sample2()


# 另外,关于进程同步,除了 multiprocessing.Lock() 之外,还有
# multiprocessing.Semaphore()
#   可以将 Semaphore 理解为一个许可证中心,该许可证中心的许可证数量是有限的
#   进程想要执行就要先从许可证中心获取一个许可证(如果许可证中心的许可证已经发完了,那就等着,等着其它进程归还许可证),执行完了再还回去
# multiprocessing.Barrier()
#   有个屏障,当所有参与者都到达屏障后,屏障解除

standardLib/process_share.py

# 本例用于演示如何在 python 的多进程中做数据共享

import multiprocessing
from multiprocessing import shared_memory
from ctypes import c_char_p
import os

def func1(i, lock, sm_name):
    lock.acquire()
    # 根据指定的名称获相关的 SharedMemory 对象
    sm = shared_memory.SharedMemory(sm_name)

    # 获取共享内存中的数据
    s = bytes(sm.buf[0:]).decode().strip("\x00")
    i = 0 if s == '' else int(s)
    i += 1
    b = str(i).encode()
    # 设置共享内存中的数据
    sm.buf[0:len(b)] = b

    # 关闭共享内存(数据不会被销毁,可以随时再打开)
    sm.close()
    lock.release()

# 通过 SharedMemory() 在多进程之间共享内存
def sample1():
    l=[]

    # 创建一个新的 SharedMemory 实例,大小为 256 字节
    sm = shared_memory.SharedMemory(create=True, size=256)
    lock = multiprocessing.Lock()
    for i in range(10):
        p = multiprocessing.Process(target=func1, args=(i,lock,sm.name))
        p.daemon = True
        p.start()
        l.append(p)
    for p in l:
        p.join()

    # 获取共享内存中的数据
    result = bytes(sm.buf[0:]).decode().strip("\x00")
    # 关闭共享内存(数据不会被销毁,可以随时再打开)
    sm.close()
    # 释放共享内存(数据会被销毁)
    sm.unlink()

    print('sample1 ok', result)



def func2(i, lock,v_i, v_d,v_c_char_p,m_dict,m_list):
    with v_i.get_lock(): # 支持通过 get_lock() 加锁
        v_i.value += 1
    with v_d.get_lock(): # 支持通过 get_lock() 加锁
        v_d.value += 1.1

    # 需要手动加锁
    lock.acquire()
    v_c_char_p.value += str(i)
    m_dict["k"] += str(i)
    m_list.append(i)
    lock.release()

# 通过 multiprocessing.Value() 和 multiprocessing.Manager() 在多进程之间共享整型,浮点型,字符串,字典,列表等数据
def sample2():
    v_i = multiprocessing.Value('i', 0) # 整型
    v_d = multiprocessing.Value('d', 0.0) # 浮点型
    v_c_char_p = multiprocessing.Manager().Value(c_char_p, "hello") # 字符串
    m_dict = multiprocessing.Manager().dict() # 字典表
    m_dict["k"] = "hello"
    m_list = multiprocessing.Manager().list() # 列表

    # 注:创建一个 multiprocessing.Manager() 时,它会启动一个新的进程来管理这些被共享的对象
    manager = multiprocessing.Manager()
    # 获取 multiprocessing.Manager() 启动的进程的 id
    manager_pid = manager._process.pid
    print("Manager process ID:", manager_pid)
    # 杀掉 multiprocessing.Manager() 启动的进程
    manager.shutdown()
    
    lock = multiprocessing.Lock()
    p_list = [multiprocessing.Process(target=func2, args=(i,lock,v_i,v_d,v_c_char_p,m_dict,m_list)) for i in range(10)]
    for p in p_list:
        p.start()
    for p in p_list:
        p.join()

    print('sample2 ok', v_i.value, v_d.value, v_c_char_p.value, m_dict["k"], m_list)


if __name__ == '__main__':
    sample1()
    sample2()

standardLib/process_communication.py

# 本例用于演示如何在 python 的多进程中做数据通信

import os, time
import multiprocessing
import multiprocessing.connection
import queue
 
# 子进程的示例(继承自 multiprocessing.Process)
class MyChildProcess1(multiprocessing.Process):
    def __init__(self, conn):
        multiprocessing.Process.__init__(self)
        self.conn = conn
 
    def run(self):
        try:
            # poll() 阻塞,直到可以确定连接对象中是否有数据可以读取
            #   None 代表永不超时;如果指定一个数字则代表最大阻塞秒数
            while self.conn.poll(None):
                # 接收另一个连接发来的数据
                data = self.conn.recv()
                print(f'child recv:{data}')
                time.sleep(0.1)
                # 发送数据给另一个连接
                self.conn.send(data.upper())
        except EOFError:
            print("EOFError")

# 通过管道的方式,即 multiprocessing.Pipe() 实现进程间通信的示例
def sample1():
    # 实例化一个 multiprocessing.Pipe() 并返回一对连接,用于通信
    # 子进程用 conn1
    # 主进程用 conn2
    conn1, conn2 = multiprocessing.Pipe()
    p1 = MyChildProcess1(conn1) # 在 MyChildProcess1() 的 __init__() 中通过 multiprocessing.Process() 启动新的进程
    p1.daemon = True
    p1.start()
    for data in ['a', 'b', 'c', 'd']:
        # conn2 发数据给 conn1
        conn2.send(data)
        # conn2 接收 conn1 发来的数据
        recv_data = conn2.recv()
        print(f'main recv:{recv_data}')

    time.sleep(2)

    # 关闭连接
    conn1.close()
    conn2.close()

    p1.join()



# 子进程的示例(继承自 multiprocessing.Process)
class MyChildProcess2(multiprocessing.Process):
    # 注:主进程的发送队列就是子进程的接收队列,主进程的接收队列就是子进程的发送队列
    def __init__(self, recv_queue, send_queue):
        multiprocessing.Process.__init__(self)
        self.recv_queue = recv_queue
        self.send_queue = send_queue
 
    def run(self):
        try:
            while True:
                # 从队列中读取数据(阻塞,并指定超时时间为 2 秒。超时时间为 None 则永不超时)
                data = self.recv_queue.get(block=True, timeout=2)
                print(f'子进程 {os.getpid()} recv:{data}')
                time.sleep(0.1)
                # 把数据放入队列
                self.send_queue.put(data.upper())
        except queue.Empty:
            print(f'子进程 {os.getpid()} 超过 2 秒未收到数据')
    
# 通过队列的方式,即 multiprocessing.Queue() 实现进程间通信的示例
def sample2():
    # 创建发送队列和接收队列
    # 另外,如果想判断队列中的任务是否执行完毕的话可以使用 multiprocessing.JoinableQueue([maxsize]),它增加了 join() 和 task_done()
    #   比如,主进程向一个 queue 放入了多条数据,然后调用 queue.join() 阻塞,子进程从 queue 中每处理一条数据就调用一次 queue.task_done(),这样数据都处理完成后 queue.join() 就会停止阻塞
    send_queue = multiprocessing.Queue()
    recv_queue = multiprocessing.Queue()
    # 队列可以交给多个进程处理,自带并发处理逻辑,不用担心多个进程同时接收或发送的问题
    p1 = MyChildProcess2(send_queue, recv_queue) # 在 MyChildProcess2() 的 __init__() 中通过 multiprocessing.Process() 启动新的进程
    p2 = MyChildProcess2(send_queue, recv_queue) # 在 MyChildProcess2() 的 __init__() 中通过 multiprocessing.Process() 启动新的进程
    p1.daemon = True
    p2.daemon = True
    p1.start()
    p2.start()
    for data in ['a', 'b', 'c', 'd']:
        # 把数据放入队列
        send_queue.put(data)
        # 从队列中读取数据
        recv_data = recv_queue.get()
        print(f'main recv():{recv_data}')

    time.sleep(3)
    
    # 关闭队列
    send_queue.close()
    recv_queue.close()
    
    # 强制杀死指定的进程
    # p1.kill()
    # p2.kill()

    # 阻塞并等待指定的进程执行完毕
    p1.join()
    p2.join()


if __name__ == '__main__':
    sample1()
    sample2()
    

速战速决 Python https://github.com/webabcd/PythonSample
作者 webabcd

posted @ 2022-01-20 16:16  webabcd  阅读(63)  评论(0编辑  收藏  举报