并发编程学习笔记

今日内容概要

  • 创建进程的多种方式
  • join方法
  • 进程间数据默认隔离
  • 进程间通信(IPC机制)
  • 生产者消费者模型
  • 进程相关方法
  • 守护进程
  • 僵尸进程与孤儿进程
  • 互斥锁

今日内容详细

创建进程的多种方式

创建进程的方式有两种:

第一种是双击桌面的程序图标

第二种是代码创建进程

创建进程的本质:在内存中申请一块内存空间用于运行相应的程序代码。一个进程对应在内存中就是一块独立的内存空间,多个进程对应在内存中就是多块独立的内存空间,进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块。以下是代码创建进程的二种方式。

第一种方式

from multiprocessing import Process
import time

def task(name):
    print(f'{name}正在运行')
    time.sleep(3)
    print(f'{name}运行结束')


if __name__ == '__main__':
    p = Process(target=task, args=('jason',))  # 创建一个进程对象
    p.start()  # 告诉操作系统创建一个进程(异步操作)
    # task('jason')  # 普通的函数调用是同步操作
    print('主进程')

第二种方式

from multiprocessing import Process
import time

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

    def run(self):
        print(f'{self.name}正在运行')
        time.sleep(5)
        print(f'{self.name}运行结束')


if __name__ == '__main__':
    obj = MyProcess('jason')
    obj.start()
    print('主进程')

强调:创建进程的代码在不同的操作系统中 底层原理有区别!!!

在windows中 创建进程类似于导入模块

​ if __ name__ == '__ main__': 启动脚本

​ 创建进程的代码必须写在__ main__子代码中,否则会直接报错 因为在无限制创建进程

在mac、linux中 创建进程类似于直接拷贝

​ 不需要启动脚本 但是为了兼容性 也可以使用

join方法

join:主进程等待子进程运行结束之后再运行

推导步骤1:直接在主进程代码中添加time.sleep()
    不合理 因为无法准确把握子进程执行的时间
推导步骤2:join方法
    很合理!!!

def task(name, n):
    print(f'{name}正在运行')
    time.sleep(n)
    print(f'{name}运行结束')


if __name__ == '__main__':
    p1 = Process(target=task, args=('jason', 1))  # args就是通过元组的形式给函数传参
    p2 = Process(target=task, args=('kevin', 2))  # 也可以通过kwargs={'name':'jason', 'n':1} 太麻烦 没必要
    p3 = Process(target=task, args=('jerry', 3))
    start_time = time.time()
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    end_time = time.time() - start_time
    print('总耗时:%s' % end_time)  # 总耗时:3.015652894973755
    print('主进程')
''' 如果是一个start一个join交替执行 那么总耗时就是各个任务耗时总和
    一定要看准join的执行位置 以及多任务情况下等待的目标 '''

进程间数据默认隔离

多个进程数据彼此之间默认是相互隔离的
    如果真的想交互 需要借助于'管道'或者'队列'

from multiprocessing import Process

money = 100

def task():
    global money  # 局部修改全局不可变类型
    money = 666
    print('子进程打印的money', money) # 666


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()  # 确保子进程代码运行结束再打印money
    print('父进程打印的money', money) # 100

进程间通信(IPC机制)

什么是队列:先进先出

from multiprocessing import Queue

1.创建队列对象
q = Queue(3)  # 括号内指定队列可以容纳的数据个数 默认:2147483647
2.往队列添加数据
q.put(111)
print(q.full())  # 判断队列是否已经存满
q.put(222)
q.put(333)
print(q.full())
q.put(444)  # 超出数据存放极限 那么程序一致处于阻塞态 直到队列中有数据被取出
3.从队列中取数据
print(q.get_nowait())
print(q.get_nowait())
print(q.get_nowait())  # 队列中如果没有数据可取 直接报错
print(q.get())
print(q.empty())  # 判断队列是否已经空了
print(q.get())
print(q.get())
print(q.empty())
print(q.get())  # 超出数据获取极限 那么程序一致处于阻塞态 直到队列中有数据被添加
"""
    q.full()
    q.empty()
    q.get_nowait()
上述方法在多进程下不能准确使用(失效)!!!
"""

IPC机制

1.主进程与子进程通信

2.子进程与子进程通信

from multiprocessing import Queue, Process


def procedure(q):
    q.put('子进程procedure往队里中添加了数据')


def consumer(q):
    print('子进程的consumer从队列中获取数据', q.get())


if __name__ == '__main__':
    q = Queue()  # 在主进程中产生q对象 确保所有的子进程使用的是相同的q
    p1 = Process(target=procedure, args=(q,))
    p2 = Process(target=consumer, args=(q,))
    p1.start()
    p2.start()
    print('主进程')

生产者消费者模型

完整的生产者消费者模型至少有三个部分 作用
生产者 产生数据
消息队列/数据库 将产生的数据进行储存
消费者 处理数据

例:爬取红牛分公司

生产者:获取网页数据的代码(函数)

爬取数据

消费者:从网页数据中筛选出符合条件的数据(函数)

筛选数据

进程相关方法

进程号在Windows上可以通过命令窗口tasklist集中查询PID,在mac系统上可以通过ps -ef查看。

python查看进程号的方法:

1.current_process函数
    from multiprocessing import Process, current_process
    current_process().pid
    获取进程号的用处之一就是可以通过代码的方式管理进程
    windows      taskkill关键字  结束进程
    mac/linux    kill关键字      结束进程
2.os模块
    os.getpid()     获取当前进程的进程号
    os.getppid()    获取当前进程的父进程号

销毁子进程

terminate()

判断进程是否存活

is_alive()

守护进程

如何理解守护?

伴随着守护对象的存活而存活 死亡而死亡

from multiprocessing import Process
import time


def task(name):
    print('大内总管:%s存活' % name)
    time.sleep(3)
    print('大内总管:%s嗝屁' % name)


if __name__ == '__main__':
    p = Process(target=task, args=('基佬',))
    p.daemon = True  # 将子进程设置为守护进程:主进程代码结束 子进程立刻结束
    p.start()
    # p.daemon = True  # 必须在start之前执行
    print('天子Jason寿终正寝!!!')

僵尸进程与孤儿进程

僵尸进程

进程已经运行结束,但是相关的资源并没有完全清空,需要父进程参与回收

孤儿进程

父进程意外死亡,子进程正常运行,该子进程就称之为孤儿进程

孤儿进程也不是没有人管,操作系统会自动分配福利院接收

互斥锁

模拟抢票

from multiprocessing import Process
import time
import json
import random


# 查票
def search(name):
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print('%s在查票 当前余票为:%s' % (name, data.get('ticket_num')))


# 买票
def buy(name):
    # 再次确认票
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟网络延迟
    time.sleep(random.randint(1, 3))
    # 判断是否有票 有就买
    if data.get('ticket_num') > 0:
        data['ticket_num'] -= 1
        with open(r'data.json', 'w', encoding='utf8') as f:
            json.dump(data, f)
        print('%s买票成功' % name)
    else:
        print('%s很倒霉 没有抢到票' % name)


def run(name):
    search(name)
    buy(name)


if __name__ == '__main__':
    for i in range(10):
        p = Process(target=run, args=('用户%s'%i, ))
        p.start()
posted @   空白o  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示