并发编程

在明白并发编程之前,我们需要了解一些名次,例如 程序, 进程, 线程

程序: 程序是一推代码,用某种语言编写的一组命令的集合。
进程: 正在进行的一个过程或者说一个任务,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是资源分配的最小单位
线程: 进程的子集,比进程更小的执行单位,线程在进程中执行,线程是cpu调度的最小单位

1. 进程

同一个程序执行多次,是开启多个进程,进程中的两大概念,并行并发, 并行是指 同时运行处理多个任务,需要多个cpu才能实现,并发 是指 处理多个任务,看起来是同时运行,是一种伪并行

1.1 进程的状态
程序运行过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪运行,阻塞
就绪: 进程已分配到除 CPU 以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态
运行: 当进程已获得处理机其程序正在处理机上执行,此时的进程状态称为执行的状态
阻塞: 在执行的进程,由于等待某个时间发生而无法执行时,便放弃处理机而处于阻塞状态,引起进程阻塞的时间可能有多种,例如,等待I/O完成申请缓冲区不能满足,等待信件(信号)等。

1.2 开启多进程的两种方式
python中开启多进程的模块在 multiprocessing 模块 Process

class Process:
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
                 *, daemon=None):

"""
group: 参数未使用,值始终为None
target: 表示调用对象,即子进程要执行的任务
args: 表示调用对象的位置参数元组,args=(1,2,'egon',)
kwargs: 表示调用对象的字典,kwargs={'name':'egon','age':18}
name为: 子进程的名称
daemon: 是否开启守护模式
"""

进程属性和方法

方法 描述
p.start() 启动进程,并调用该子进程中的 p.run()
p.join() 主进程等待子进程终止,p.join只能join主start开启的进程,不能join主 run开启的进程
p.is_alive() 验证进程是否还存活着
p.terminate() 强制终止进程
p.daemon 守护进程,如果设为True,代表为守护进程,当父进程结束,子进程也随之结束,子进程不能在创建自己的子进程,必须要在开启之前设置
p.name 进程的名称
p.pid 进程的 pid

方式一

import time
from multiprocessing import Process


def task(name):
    print(f'{name} is running')
    time.sleep(3)
    print(f'{name} is done')


if __name__ == '__main__':
    p = Process(target=task, args=('进程',))  # 创建线程
    p.start()  # 发了一个信号,申请内存空间,需要花一定时间
    print('主进程')  # 主进程
    
# 结果如下:
"""
主进程
进程 is running
进程 is done
"""

方式二

import time
from multiprocessing import Process


class OwnProcess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'{self.name} is running')
        time.sleep(3)
        print(f'{self.name} is done')


if __name__ == '__main__':
    p = OwnProcess('进程')   # 创建进程
    p.start()  # 触发 run 方法
    print('主进程')

# 结果如下:
"""
主进程
进程 is running
进程 is done
"""
p.start() 是开启进程的方式,

1.3 进程间数据是隔离的

from multiprocessing import Process

import time

n = 100


def task():
    global n
    n = 0
    print(n)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    print(p.is_alive())  # 验证进程是否还存活着的
    p.join()    # 等子进程运行完毕再执行下面代码
    print(p.is_alive())
    print('主进程', n)

# 结果:
"""
True
0
False
主进程 100
"""

1.4 僵尸进程与孤儿进程
僵尸进程: 是指一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

孤儿进程: 顾名思义,子进程还在世的时候父进程却结束了,孤儿进程是无害的,在用户机结束时终止。它有一个功能就是收养这些孤儿

1.5 守护进程
守护进程: 当子进程执行的任务 在父进程代码运行代码完毕之后就没有存在的必要了,那么该子进程就应该设施为守护进程

import time
from multiprocessing import Process


def task(name):
    print(f'{name} is running')
    time.sleep(5)
    print(f'{name} is done')


if __name__ == '__main__':
    p = Process(target=task, args=('action',))
    p.daemon = True  # 创建守护进程
    p.start()
    time.sleep(2)
    print('主进程')
 
# 结果:
"""
action is running
主进程
"""

1.6 进程互斥锁
当有 两个或以上线程在同一时刻访问同一资源,会出现一系列问题,例如进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,而加锁就能解决这个问题

import json
from multiprocessing import Process,Lock
import time
import random
import os


def search():
    """查票"""
    time.sleep(random.randint(1, 3))
    dic = json.load(open('db.txt', 'r', encoding='utf-8'))
    print(f'{os.getpid()} 查看到剩余票数{dic["count"]}')


def get():
    """购票"""
    dic = json.load(open('db.txt', 'r', encoding='utf-8'))
    if dic['count'] > 0:
        dic['count'] -= 1
        time.sleep(random.randint(1, 3))
        json.dump(dic, open('db.txt', 'w', encoding='utf-8'))
        print(f'{os.getpid()} 购票成功')


def task(mutex):
    search()
    mutex.acquire()  # 加锁
    get()
    mutex.release()  # 释放


if __name__ == '__main__':
    mutex = Lock()
    for i in range(5):
        p = Process(target=task, args=(mutex,))
        p.start()

    print('主进程')

# 结果:
"""
主进程
7533 查看到剩余票数1
7535 查看到剩余票数1
7531 查看到剩余票数1
7532 查看到剩余票数1
7534 查看到剩余票数1
7533 购票成功
"""

1.7 进程间通信
进程之间数据是相互隔离的,要想实现进程间的通信,就必须借助于一些技术才可以,比如multiprocess模块中的:队列管道,这两种方式都是可以实现进程间数据传输的,由于队列是管道+锁的方式实现的,
创建共享进程队列,Queue是多进程安全的队列,可以使用 Queue实现多进程之间的数据传递

from multiprocessing import Queue

q = Queue(3)
q.put('first')
q.put(2)
q.put(['count', 3])
# q.put('fourth')  # 默认阻塞
# q.put_nowait('fourth')  # 不阻塞
# q.put('fourth', block=False)  # 不阻塞 ,报错
# q.put('fourth', block=True, timeout=5)  # 设置超时时间

print(q.get())
print(q.get())
print(q.get())

# print(q.get())  # 默认阻塞
# print(q.get_nowait())  # 默认不阻塞
# print(q.get(block=False))  # 不阻塞
# print(q.get(block=True, timeout=5))   # 设置超时时间

生产者模式消费者模式
生产者消费者模式是 通过一个容器来解决生产者和消费者强耦合的问题,生产者和消费者 彼此之间不直接通信,而是 通过阻塞队列来进行通讯,所以 生产者生产完数据之后不用等待消费者来处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列中取。

import time
import random
from multiprocessing import Queue, Process


def producer(name, food, q):
    for i in range(3):
        res = f'{food}{i}'
        time.sleep(random.randint(1, 3))
        print(f'厨师{name}生产了{res}')
        # consumer('action', res)
        q.put(res)


def consumer(name, q):
    while True:
        res = q.get()
        if res is None:
            break
        time.sleep(random.randint(1, 3))
        print(f'吃货{name} 吃了{res}')


if __name__ == '__main__':
    # 队列
    q = Queue()
    # 生产者
    p1 = Process(target=producer, args=('wu_chang1', '火锅', q))
    p2 = Process(target=producer, args=('wu_chang2', '火锅', q))
    # 消费者
    c1 = Process(target=consumer, args=('action1', q))
    c2 = Process(target=consumer, args=('action2', q))
    c3 = Process(target=consumer, args=('action3', q))
    # 开启线程
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    c3.start()

    p1.join()
    p2.join()

    q.put(None)   # 发送结束信号
    q.put(None)   # 发送结束信号
    q.put(None)   # 发送结束信号

    c1.join()
    c2.join()
    c3.join()

    print('主进程')
这样又一个不好的地方,就是每一个进程都要发送一个结束信号,对开发这非常不友好
import time
import random
from multiprocessing import Process, JoinableQueue


def producer(name, food, q):
    for i in range(3):
        res = f'{food}{i}'
        time.sleep(random.randint(1, 3))
        print(f'厨师{name}生产了{res}')
        # consumer('action', res)
        q.put(res)


def consumer(name, q):
    while True:
        res = q.get()
        time.sleep(random.randint(1, 3))
        print(f'吃货{name} 吃了{res}')
        q.task_done()


if __name__ == '__main__':
    # 队列
    q = JoinableQueue()
    # 生产者
    p1 = Process(target=producer, args=('wu_chang1', '火锅', q))
    p2 = Process(target=producer, args=('wu_chang2', '火锅', q))
    # 消费者
    c1 = Process(target=consumer, args=('action1', q))
    c2 = Process(target=consumer, args=('action2', q))
    c3 = Process(target=consumer, args=('action3', q))
    c1.daemon = True
    c2.daemon = True
    c3.daemon = True
    # 开启线程
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    c3.start()

    p1.join()
    p2.join()
    q.join()

    print('主进程')
将消费者设置为守护线程,当 JoinableQueue 队列中没有东西过后,就随着主进程一起结束

无常
21 声望0 粉丝