Python基础之网络编程:6、网络并发编程理论与实操(二)

一、任务运行的多种方式

1、同步与异步

根据进程和函数之间的通讯机制,函数可分为同步和异步

同步与异步是为了表达任务的提交方式

  • 同步:指提交任务后,直接得到任务的最终结果
    • eg:在取款机前排队取款,直到排队取到钱之后离开
  • 异步:指提交任务后,不等待任务的结果,直接去做另外的事情,任务完成后结果会反馈回来
    • eg:指在银行取钱,领到取票机的小票后,不用一直排队,可以去做其他的事情,到号时,银行会叫号

2、阻塞与非阻塞

指函数或方法调用的时候,结果是否会立即返回

  • 阻塞:执行任务后会立即返回任务的结果
  • 非阻塞:执行任务后,并不会立即返回结果,就绪态、运行态

3、综合使用

综合使用指同步与非同步、阻塞与非阻塞四种状态两两结合使用

  • 同步阻塞
    • 指调用任务后,遇到阻塞,但是过程中什么也干不了,只能等待任务的最终结果
  • 同步非阻塞
    • 指调用任务后,任务在顺利进行,运行通畅,直到获取任务结果
  • 异步阻塞
    • 指调用任务后,任务遇到阻塞,没有办法直接获取到最终结果,但是调用者可以去处理其他的事情,并不会影响调用者处理其他事情,等原任务执行结束后反馈结果即可
  • 异步非阻塞
    • 指调用者提交一个任务后,立即提交其他的任务,并且提交的多个任务都可以通畅运行,立即获得结果,这种状态的效率极高

二、创建进程的多种方式

​ 通常,用户使用计算机,创建进程都是通过双击电脑桌面上的应用图标来创建进程,但我们程序员在使用python时,可以直接通过代码来创建进程,并且可以同时创建多个进程,且进程与进程之间互不干扰,可以高效的提高任务完成率

1、双击电脑桌面图标

2、通过代码创建

须知:

  • windows
    • 以导入模块的方式创建进程
  • linux/mac
    • 以拷贝代码的方式创建进程

1、代码创建进程

方式一

​ 函数方式创建

from multiprocessing import Process
import time


def task(name):
     print('task is running',name)
     time.sleep(3)
     print('task is over',name)
        
if __name__ == '__main__':
     # p1 = Process(target=task, args=('jason',))  # 位置参数
     p1 = Process(target=task, kwargs={'name':'jason123'})  # 关键字参数
     p1.start()  # 异步 告诉操作系统创建一个新的进程 并在该进程中执行task函数
     task()  # 同步
     print('主')

方式二

​ 继承类的方式创建

from multiprocessing import Process
import time


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

    def run(self):
        print('run is running', self.name, self.age)
        time.sleep(3)
        print('run is over', self.name, self.age)


if __name__ == '__main__':
    obj = MyProcess('jason', 123)
    obj.start()
    print('主')

2、进程间数据隔离

​ 进程隔离是指在同一台计算机上多个同时运行的进程,进程与进程之间数据默认是隔离的

​ eg:在主进程定义一个变量,使用主进程创建多个进程,在创建的多个进程内修改、重新定义主进程的变量,每个进程内的变量互不干扰,包括主进程。

代码表现:

from multiprocessing import Process
import time

money = 1000


def task():
    global money
    money = 666
    print('子进程的task函数查看money', money)


if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()  # 创建子进程
    time.sleep(3)  # 主进程代码等待3秒
    print(money)  # 主进程代码打印money

3、进程join方法

​ 使用主进程创建多个子进程后,代码运行到主进程创建的子进程代码后,通常子进程代码是异步执行,并不会干扰主进程代码执行顺序,通过join方法可使异步执行的代码变为同步执行,主进程会等待子进程代码运行结束后向下执行,join方法需定义在子进程启动代码后,否则会影响同步执行的结果

使用方法:

​ 通过定义的子进程对象‘点’join()的方式使用

代码表现:

from multiprocessing import Process
import time


def task(name, n):
    print('%s is running' % name)
    time.sleep(n)
    print('%s is over' % name)


if __name__ == '__main__':
    p1 = Process(target=task, args=('jason1', 1))
    p2 = Process(target=task, args=('jason2', 2))
    p3 = Process(target=task, args=('jason3', 3))
    # p.start()  # 异步
    '''主进程代码等待子进程代码运行结束再执行'''
    # p.join()
    # print('主')
    start_time = time.time()
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    p3.start()
    p3.join()
    # p1.join()
    # p2.join()
    # p3.join()
    print(time.time() - start_time)  # 3秒多

4、IPC机制

IPC>>>:进程间通讯机制

相当于消息队列,储存数据的地方,同一主进程下的所有子进程共同存、取数据的地方,大家都可以使用

使用方法:

​ 1、导入multiprocessing模块下的Queue

​ 2、通过赋值变量的方式创建消息队列

​ 3、消息队列后方括号内为储存数据的个数

​ 4、对象‘点’put():存入

​ 5、对象‘点’get():取

代码用法:

from multiprocessing import Queue


q = Queue(3)  # 括号内可以指定存储数据的个数
# 往消息队列中存放数据
q.put(111)
# print(q.full())  # 判断队列是否已满
q.put(222)
q.put(333)
# print(q.full())  # 判断队列是否已满
# 从消息队列中取出数据
print(q.get())
print(q.get())
# print(q.empty())  # 判断队列是否为空
print(q.get())
# print(q.empty())  # 判断队列是否为空
# print(q.get())
print(q.get_nowait())

"""
full() empty() 在多进程中都不能使用!!!
"""


from multiprocessing import Process, Queue


def product(q):
    q.put('子进程p添加的数据')

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


if __name__ == '__main__':
    q = Queue()
    # 主进程往队列中添加数据
    # q.put('我是主进程添加的数据')
    p1 = Process(target=consumer, args=(q,))
    p2 = Process(target=product, args=(q,))
    p1.start()
    p2.start()
    print('主')

三、生产者消费者模型

  • 生产者:

    • 负责生产数据的代码

      eg: 回想使用爬虫,在网页上获取数据

  • 消费者:

    • 负责处理数据的代码

    ​ eg:类似于正则,对数据进行处理,分类

该模型除了有生产者和消费者之外还必须有消息队列(只要是能够提供数据保存服务和提取服务的理论上都可以)

四、进程对象的多种方法

1、查看进程号

方式一:

1、导入multiprocessing模块下current_process方法
2、在进程下使用currnet_process().pid
3、该方法可直接查看进程的 '父' 进程,以及进程号

方式二:

1、导入os模块
2、os.getpid()
	查看当前进程的进程号
3、os.getppid()
	查看进程的 '父' 进程

2、终止进程

1、通过进程对象名‘点’terminate()
	可直接结束进程
ps: 计算机操作系统有很多命令可以直接杀死(结束)进程

3、判断进程是否存活

1、通过进程名‘点’is_alive()
	存活:Trun
	死亡:False

4、守护进程

​ 守护进程是主进程创建的进程

  • 守护进程会在主进程代码运行结束后就立即终止
  • 守护进程内无法创建子进程,否则报错
  • 守护进程和主进程之间是相互独立的

代码用法:

from multiprocessing import Process
import time


def task(name):
    print('德邦总管:%s' % name)
    time.sleep(3)
    print('德邦总管:%s' % name)


if __name__ == '__main__':
    p1 = Process(target=task, args=('大张红',))
    p1.daemon = True
    p1.start()
    time.sleep(1)
    print('恕瑞玛皇帝:小吴勇嗝屁了')

5、僵死进程与孤儿进程

5、1.僵尸进程

​ 僵尸进程是指进程运行结束后,并不会立刻销毁所有的数据,而是等一段时间后才会销毁,

​ eg:进程号、进程执行时间、进程执行结果,会将这些结果反馈给父进程查看

所有的进程都会经理僵尸进程这个阶段

5、2.孤儿进程

​ 指子进程正常运行,父进程以外死亡,操作系统会针对孤儿进程派出福利院进行接收、接管这些进程

​ 福利院:__ init __进程/进程号为1

​ 福利院会对孤儿进程进行接管、负责收集孤儿进程的数据、结果、执行时间

五、互斥锁

1、多进程错乱问题

​ 多进程错乱问题指,当我们使用主进程创建了多个子进程后,这些子进程,在同一时间查询并需要使用主进程内IPC(消息队列)内数据,但是因为是同一时间,所以会产生消息错乱问题

​ eg:主进程消息队列内只有一个数据,但是所有子进程在同一时间调用get方法后都可以查询并取到数据

​ 解决办法:可以使用互斥锁,将并发变为穿行,牺牲了效率,保证了数据的准确性和安全

多进程错乱问题代码表现:

模拟抢票软件

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()

2、互斥锁代码实操

​ 互斥锁可以解决多进程错乱问题

用法:

​ 1、在主进程生成互斥锁对象

​ 2、在子进程需要加互斥锁的代码上下方加入关键词

​ 3、只有第一个抢到互斥锁的进程对象才有资格运行代码,在代码运行结束后下一个进程按照上述流程运行

注意事项:

​ 建议将锁加在实际数据操作部分,否则会降低程序运行效率

关键词 作用
Lock 模块名
acquire 抢锁
release 释放锁

代码实操:

from multiprocessing import Process, Lock
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:
        with open(r'data.json', 'w', encoding='utf8') as f:
            data['ticket_num'] -= 1
            json.dump(data, f)
        print('%s 买票成功' % name)
    else:
        print('%s 买票失败 非常可怜 没车回去了!!!' % name)


def run(name, mutex):
    search(name)
    mutex.acquire()  # 抢锁
    buy(name)
    mutex.release()  # 释放锁


if __name__ == '__main__':
    mutex = Lock()  # 产生一把锁
    for i in range(10):
        p = Process(target=run, args=('用户%s号' % i, mutex))
        p.start()

六、多进程TCP服务端并发

1、代码用法

import socket
from multiprocessing import Process


def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    return server


def get_talk(sock):
    while True:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())


if __name__ == '__main__':
    server = get_server()
    while True:
        sock, addr = server.accept()
        # 开设多进程去聊天
        p = Process(target=get_talk, args=(sock,))
        p.start()
posted @ 2022-11-21 17:40  kangshong  阅读(56)  评论(0编辑  收藏  举报