Welcome to kimi's blog

并发编程理论及进程理论

并发编程理论

并发编程理论

理论基础多,实战比较少。
研究网络编程其实就是研究计算机的底层原理

1. 操作系统的发展史

1.穿孔卡片阶段
	计算机很庞大,使用起来很麻烦,每一次只能供一个使用,期间很多时候计算机都不工作
	优势:用户独占计算机,不会出现因资源被其他用户占用而等待的现象,但资源的利用率低,比如:程序员独占计算机  为所欲为
	劣势:计算机利用率太低,浪费资源
	
2.联机批处理系统
	提前使用磁带一次性录入多个程序员编写的程序,然后交给计算机执行
	优势:CPU工作效率有所提升,不用反复等待程序录入
3.脱机批处理系统
	极大的提升了CPU的利用率
	总结:CPU提升利用率的过程
	

2. 多道技术

在学习并发编程的过程中,不做可以刻意提醒的条件下,默认一台计算机就一个CPU(只有一个干活的人)

1.单道技术

所有的程序排队执行  过程中不能重合

2.多道技术

利用空闲时间提前准备其他数据,最大化提升CPU利用率

3.多道技术详细

1.切换
 	计算机的CPU在两种情况下会切换(不让你用,给别人用)
 		1.程序有IO操作
 			输入/输出操作
 			  input、time.sleep、read、write
 		2.程序长时间占用CPU
 			多个任务同时启动,CPU都要被参与程序运行,要雨露均沾
 			  
 2.保存状态
 	CPU每次切换走之前都需要保存当前操作状态,下次切换回来基于上次的进度继续执行
 	
 eg:
  一个人要同时使用五台打印机打印资料,请问如何要一个人连续使用打印机打印资料?
	启动第一台打印机打印资料之后,第一台在打印过程中就可以去启动第二台打印机执行打印任务,依次类推...

3. 进程理论

1.进程的概念

	进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。狭义的定义:进程是正在运行的程序的实例。广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
	操作系统引入进程概念的原因:从理论角度看,是对正在运行的程序过程的抽象;从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。

2.进程的特征

动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

3.进程与程序的区别

	程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。
	
简单理解为:
	程序:一堆死代码(还没有运行起来)
	进程:正在运行的程序(被运行起来)

注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。

4. 进程调度

​ 要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随机进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。

进程的调度算法
	1.FCFS(先来先服务)调度算法
	   是一种简短的调度算法,该算法既可用于作业调度,也可用于进程调度,FCFS算法比较有利于长作业(进程),而不利于短作业,>>>>FCFS算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)
	2.短作业优先调度算法
	    短作业(进程)优先调度算法(SJ/PE)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度,不能保证紧迫性作业(进程)被及时处理;作业的长度只是被估算出来的。
	3.时间片轮转法+多级反馈队列(目前还在用)
		将时间均分>然后根据进程时间长短再分等级
		等级越靠下表示耗时越长,每次分到的时间越多,但是优先级越低
		
	    
FCFS——先来先服务调度算法、SPF——短进程优先调度算法、优先级调度算法、时间片轮转调度算法、最短剩余时间优先调度算法 

4.1 时间片轮转法

	时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。
      显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。
在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
      在轮转法中,加入到就绪队列的进程有3种情况:
      一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
      另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
      第三种情况就是新创建进程进入就绪队列。
      如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。  

4.2 多级反馈队列

	前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。
	而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
	(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
	(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。

	(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。

5. 进程的并行与并发

  • 并行

    ​ 多个进程同时执行 ,必须要有多个CPU参与 单个CPU无法实现并行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )

  • 并发

    ​ 并发是指资源有限的情况下,两者(多者)替轮流使用资源。多个进程看上去像同行执行 (单个CPU可以实现,多个CPU肯定也可以)比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。

  • 高并发

    ​ 评论程序同时服务客户端数量的能力

判断下列两句话孰对孰错
  我写的程序很牛逼,运行起来之后可以实现14个亿的并行量
  	并行量必须要有对等的CPU才可以实现(×)
  我写的程序很牛逼,运行起来之后可以实现14个亿的并发量
  	合情合理 完全可以实现	以后我们的项目一般都会追求高并发(√)
ps:目前国内可以说是最牛逼的>>>:12306
	

进程三态状态装换图

​ 在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪态、运行态和阻塞态

image

  • 就绪态

    ​ 当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。即所有的进程在被CPU执行之前都必须先进入就绪态等待。

  • 运行态

    ​ 执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

  • 阻塞态

    ​ 阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。即进程运行过程中出现了IO操作,阻塞态无法直接进行运行态,需要先进入就绪态。

  • 创建态和终止态:进程开始和结束的状态

    进程代码演示:

image

6. 同步与异步

​ 在多进程并发中,一般认为所有的进程都是有异地属性的。

用来表达任务的提交方式

同步
	提交完任务之后原地等待任务的返回结果  期间不做任何事
	eg:去银行办理业务,选择排队等候(一直同步等待消息的通知)
异步
	提交完任务之后不愿地等待任务的返回结果  直接去做其他事  有结果自动通知
	eg:取号排队,取号之后就可以做其他事,直到被窗口喊到取的号,再去柜台办理业务

7. 阻塞与非阻塞

	用来表达任务的执行状态,也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的

阻塞
	进程进入阻塞态
非阻塞
	程序处于就绪态和运行态。

	当一个任务因调用或IO要获取某个结果,阻塞调用必须等待调用的结果,非阻塞调用则可以不必等待这个结果而使进程始终处于就绪态和运行态。

8. 综合使用

同步阻塞:效率最低,银行取号,按部就班的排队,期间啥事也不做
同步非阻塞:原地等待,可以原地做一些事
异步阻塞:提交任务去做其他事,但还是要等cpu
异步非阻塞(******)
	效率最高
	
	很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞。

9. 进程的开始与结束

1.进程的开始

1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)

 2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)

 3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)

 4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)

2.进程的结束

1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

 2. 出错退出(自愿,python a.py中a.py不存在)

 3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)

 4. 被其他进程杀死(非自愿,如kill -9)

在python程序中的进程操作

1. 创建进程的多种方式

1.1 multiprocessing模块

内置模块

1.第一种方式

"""
1.鼠标双击软件图标;
2.python代码创建进程
"""
from multiprocessing import Process
import time

def task(name,pwd):  # 位置传参
    print('task is running',name,pwd)
    time.sleep(3)
    print('task is over',name,pwd)
   
# def task(name):  # 关键字传参
#     print('task is running', name)
#     time.sleep(3)
#     print('task is over',name)

# def task(name):
#     print('task is running',name)
#     time.sleep(3)
#     print('task is over',name)

# 创建一个新的进程
if __name__ == '__main__':
    p1 = Process(target=task,args = ('kimi',123))  # 异步 告诉操作系统创建一个新的进程,并在该进程中执行task函数
    p2 = Process(target= task,args = ('rose',234))  # 位置传参
    # p1 = Process(target=task,kwargs={'name':'kiki',})  # 关键字传参
    # p1 = Process(target= task,args = ('rose',))
    
    # task()  # 同步
    print('主进程') # 当前进程继续执行它的程序
 
同步方式下,先执行task函数,再进行主进程
    代码结果展示:
        task is running
        task is over
        主进程
异步方式下,
    代码结果展示:先执行主程序,再执行子进程
       主进程
        task is running kimi 123
        task is running rose 234
        task is over rose 234
        task is over kimi 123

底层原理:在不同的操作系统中创建进程的底层原理不一样
windows
以导入模块的形式创建进程
linux/mac
以拷贝代码的形式创建进程"""

2.第二种方式

from multiprocessing import Process
import time

class MyProcess(Process):  # 继承Process类
    def __init__(self,name,age):   # 如果要传参,则通过这种方式,调用类名时触发
    	super().__init__()   # 派生原本的方法
        self.name = name  # 创建完一个进程后,添加新的属性
        self.age = age

    def run(self,):  # 必须要写一个run方法
        print('run is running',self.name,self.age)
        time.sleep(3)
        print('run is over',self.name,self.age)

if __name__ == '__main__':
    obj = MyProcess('kiki',18)
    obj.start() # 启动子进程,只有对象才能去点子进程
    print('主进程')
    
代码结果展示:
    主进程
    run is running kiki 18
    run is over kiki 18

1.2 进程join方法

1.基本使用

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__':
    # p= Process(target=task,args=('kimi',1))
    # p.start()  # 异步操作
     # time.sleep(3)  # 要是知不知道子进程的代码运行时间,所有这种方法行不通
    """ 主程序代码等待子进程代码运行结束再执行"""
    # p.join()  # 同步
    # print('主程序')

​ 让主进程等待这个子进程结束后再继续执行后续操作

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=('kimi', 1))
    p2 = Process(target=task, args=('kimi', 2))
    p3 = Process(target=task, args=('kimi', 3))
    
	#1. 异步并发
    # start_time = time.time()
    # p1.start()
    # p2.start()
    # p3.start()
    # p1.join()
    # p2.join()
    # p3.join()
    # print(time.time()-start_time)
    """
    结果展示: # 异步并发
    kimi is running 
    kimi is running 
    kimi is running 
    kimi is over 
    kimi is over 
    kimi is over 
    3.0810062885284424
    p1 p2 p3 几乎同时启动的,join等p1需要1s,等p2需要1s,等p3需要1s,总共就是3s多
    """

	#2.同步串行
    start_time = time.time()
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    p3.start()
    p3.join()
    print(time.time()-start_time)
    """
    结果展示:  # 同步串行
    kimi is running 
    kimi is over 
    kimi is running 
    kimi is over 
    kimi is running 
    kimi is over 
    6.186866998672485
    p2是等p1结束后才启动,p3是等p2结束后才启动的,一共1+2+3 =6s多
    """

1.3 进程间数据隔离

同一台计算机的多个进程数据是严格意义上的物理隔离(默认情况下)
from multiprocessing import  Process
import time

money = 8888

def task():
    global money
    money = 666
    print('子进程的task函数的money',money)
    """子进程的全局money是已经修改成666"""

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

    print(money)  # 8888  主进程打印money
    """ 这个money是主进程的money"""

上述代码看似是更改了全局变量的money ,实际上我们只修改了子进程的全局money=666,但是不会影响主进程的任何数据,如下图:

image

1.4 进程间通信:IPC机制

IPC:进程间通信,为了能使多进程协调工作而让彼此隔离的进程有沟通的能力

消息队列

​ 存储数据的地方,所有主进程和子进程都可以存数据或者取数据

from multiprocessing import Queue

q = Queue(3) # 括号内指定存储数据的个数,默认可以存SEM_VALUE_MAX =
#1. 往消息队列种存放数据
q.put(111)
print(q.full())  # 判断队列是否已满  False
q.put(222)
q.put(333)
print(q.full())  # 判断队列是否已满  True
#2.从消息队列取出数据
print(q.get())  # 111
print(q.empty())  # 判断队列是否为空  False
print(q.get())  # 222
print(q.get())  # 333
# print(q.empty())  # 判断队列是否为空  True
# print(q.get())  # 数据拿不到值,就会一直等着,处于阻塞态

print(q.get_nowait())  # 如果队列没有数据,会直接报错
"""full() empty()  在多进程种都不能使用,判断会出现失误,
执行q.full()是True,在极限情况,当队列满的时候,当一个进程执行q.get(),队列的数据会被拿走
执行q.empty()是true,在极限情况,队列是空的时候,你执行q.empty是空的下一秒有一个进程往里面存数据,此时的队列不是空的
,"""

进程间的实操--多进程中使用

from multiprocessing import Process,Queue

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

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

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

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

if __name__ == '__main__':
    c = Queue()
    p1 = Process(target=consumer,args=(c,))
    p2 = Process(target=product,args=(c,))
    p1.start()
    p2.start()
    print('主进程')

1.6 生产者消费者模型

"""回想爬虫"""
生产者
	负责产生数据的‘人’
消费者
	负责处理数据的‘人’
	
该模型除了有生产者和消费者之外还必须有消息队列(只要是能够提供数据保存服务和提取服务的理论上都可以)

生产消费模型图

image

2. 进程对象的多种方法

命令窗口查看进程

image

1.如何查看进程号
  1.1 multiprocessing模块查看进程号
	from multiprocessing import Process,current_process
    def task():
        print(current_process())  # <Process name='Process-1' parent=26120 started>
        print(current_process().pid)  # 3212进程号

    if __name__ == '__main__':
        p1 = Process(target=task)
        p1.start()
        p1.join()
        print(current_process())  # <_MainProcess name='MainProcess' parent=None started>
        print(current_process().pid) # 26120 进程号
    
  1.2 os模块查看进程号
  	from multiprocessing import Process
    import os
    def task():
        print('子进程',os.getpid())  # 子进程 7572

        print('获取当前进程的主进程号',os.getppid())  # 获取当前进程的主进程号 24900

    if __name__ == '__main__':
        p1 = Process(target=task)
        p1.start()
        # p1.terminate()  # 终止进程  类似if判断
        # time.sleep(0.01)  # 睡0.01秒 ,下面结果就是FALSE
        # print(p1.is_alive())  # True
        print('主进程',os.getpid())   # 主进程 24900
    
2.终止进程
	p1.terminate()  # 终止进程
	命令窗口>>>help taskkill
	eg: 
	p1.terminate(pid)  # 括号里面填写pid
	taskkill /F /PID PID号
	
3.判断进程是否存活
	print(p1.is_alive())  # True

3. 守护进程

守护进程会随着守护的进程结束而立刻结束
eg:A是B的守护进程,一旦B进程结束了,A也会立刻结束

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=('kiki',))
    p1.daemon = True  # 必须要放在start前面,也就是子进程开启之前,表示守护主进程
    p1.start()
    time.sleep(1)
    # p1.daemon = True # 放在这里,下面根本就不会走
    print('主进程结束')

4. 僵尸进程与孤儿进程

僵尸进程

	进程执行完毕后并不会立刻销毁所有的数据,会有一些信息短暂保留下来,比如进程号、进程执行时间、进程消耗功率等给父进程查看
	ps:所有的进程都会变成僵尸进程

孤儿进程

	子进程正常运行,,父进程意外死亡,操作系统针对孤儿进程会派遣福利院管理

5. 多进程数据错乱问题

模拟抢票软件

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()
   
"""
多进程操作数据很可能会造成数据错乱>>>:互斥锁
	互斥锁
		将并发变成串行 牺牲了效率但是保障了数据的安全
"""


6. 多进程实现TCP服务端并发

服务端

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

客户端

import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    client.send(b'hello friend')
    data = client.recv(1024)
    print(data)

"""
1.可以在while True 前面添加 循环  for i in range(100) ,自动产生100个客户端
2.手动产生
"""

效果如下图:

image

6. 互斥锁代码实操

锁:建议只加载操作数据的部分,否则整个程序的效率会极低

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):
    # mutex.acquire()  如果在此处加,程序只能一个人先查票买票结束周,第二人开始查票买票,依次类推,极其不合理
    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()
"""
锁有很多种 但是作用都一样
	行锁 表锁 ...
"""

代码结果展示:
用户0号查看票 目前剩余:1
用户1号查看票 目前剩余:1
用户2号查看票 目前剩余:1
用户3号查看票 目前剩余:1
用户4号查看票 目前剩余:1
用户5号查看票 目前剩余:1
用户6号查看票 目前剩余:1
用户7号查看票 目前剩余:1
用户8号查看票 目前剩余:1
用户9号查看票 目前剩余:1
用户0号 买票成功
用户1号 买票失败 非常可怜 没车回去了!!!
用户2号 买票失败 非常可怜 没车回去了!!!
用户3号 买票失败 非常可怜 没车回去了!!!
用户4号 买票失败 非常可怜 没车回去了!!!
用户5号 买票失败 非常可怜 没车回去了!!!
用户6号 买票失败 非常可怜 没车回去了!!!
用户7号 买票失败 非常可怜 没车回去了!!!
用户8号 买票失败 非常可怜 没车回去了!!!
用户9号 买票失败 非常可怜 没车回去了!!!

json文件数据

{"ticket_num": 1}

结果说明:通过 mutex = Lock() # 产生一把锁,在程序中添加了互斥锁了,将枪锁的操作 mutex.acquire() 放置数据操作buy(name)之前,将释放锁的操作mutex.release() 放置操作数据buy(name)之后,只要有一个线程枪锁成功了,才能直接去买票,其余的线程都不能直接买票。

注意:for循环可以看作是多个进程同时进行的,但是进程是不能同时进程很多,因为会增加了CPU的压力

posted @ 2022-11-20 21:25  魔女宅急便  阅读(110)  评论(0)    收藏  举报
Title