并发编程

1、进程基本操作

os.getpid获取进程id:

import os
import time
print('start')
time.sleep(40)  #配合time.sleep()
print(os.getpid(),os.getppid(),'end')  #获取进程id os.getpid()  os.getppid()  父进程id
------------结果:
start
5028 1960 end  # 5028 进程id 当前文件执行的进程id,程序结束,进程停止;1960 父进程pycharm id  

# pid   process id
# ppid  parent process id
# 子进程
# 父进程 在父进程中创建子进程
# 在pycharm中启动的所有py程序都是pycharm的子进程

1557764818794

进程创建,执行本文件内的函数:

import os
import time
from multiprocessing import Process  #[1]从多进程ing导入进程P  Process类

def func():
    print('start',os.getpid())  #[3]开启的进程函数做的操作:获取进程id,因为这是子进程执行的程序内容
    time.sleep(1)		#所以这里的os.getpid()表示的是这个开启的进程的pid
    print('end',os.getpid())

if __name__ == '__main__':
    p = Process(target=func)  #[2]创建进程对象  Process(目标=函数名) #目标=xx#查看源码可知  Process类init方法中有传参 target
    p.start()   # 异步 调用开启进程的方法 但是并不等待这个进程真的开启,
    print('main :',os.getpid())
----------------结果:
main : 3012  #[4]先执行start,但是先打印main。因为是异步的,调用开启进程的方法,p.start 执行之后就会创建进程,将传进去取的target加括号()执行。无需等待这个子进程开启和执行,当前程序继续往下执行。
start 5748  #[5]这两个获取的id 都是子进程程序运行里的,获取的都是子进程pid,值是一样的。
end 5748

>tasklist|findstr "py" #Windows查看进程的命令

1557765208621

1557816673647

进程是并发的:

import os
import time
from multiprocessing import Process

def eat():
    print('start eating',os.getpid())
    time.sleep(1)
    print('end eating',os.getpid())

def sleep():
    print('start sleeping',os.getpid())
    time.sleep(1)
    prinst('end sleeping',os.getpid())

if __name__ == '__main__':
    p1 = Process(target=eat)    # [1]创建一个即将要执行eat函数的进程对象
    p1.start()                  # [2]开启一个进程
    p2 = Process(target=sleep)  # [3]创建一个即将要执行sleep函数的进程对象
    p2.start()                  # [4]开启进程
    print('main :',os.getpid())
--------------------------结果:
main : 6216  #[5]异步,父进程没有等到子进程结束再往下执行,由于进程创建需要花费时间,所有在子进程创建的过程中,主进程已经往下执行并打印出主进程的pid.
start eating 6468  #[6]由此处可知,代码先创建并执行p1进程,再创建并执行p2进程,但是二者并没有说先执行完p1
start sleeping 7120 #进程,然后才执行p2进程。也就是说创建多个进程,进程运行是异步的,是并发执行。
end eating 6468
end sleeping 7120

if __name__ == '__main__':的作用:以及操作系统创建进程的方式区别

import os
import time
from multiprocessing import Process
def func():
    print('start',os.getpid())
    time.sleep(1)
    print('end',os.getpid())

if __name__ == '__main__':
    p = Process(target=func)
    p.start()   # 异步 调用开启进程的方法 但是并不等待这个进程真的开启
    print('main :',os.getpid())
-------------结果:
main : 6420
start 6736
end 6736



# 操作系统创建进程的方式不同
# windows操作系统执行开启进程的代码
    # 实际上新的子进程需要通过import父进程的代码来完成数据的导入工作
    # 所以有一些内容我们只希望在父进程中完成,就写在if __name__ == '__main__':下面
# ios linux操作系统创建进程 fork

错误一:
如果 在windows里面没有使用if __name__ == '__main__':,那么会报错:
 if __name__ == '__main__':
                freeze_support()
 The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.
  • if __name__ == '__main__':的作用:

  • 两个文件打印__name__,分别执行文件之后打印__main__的作用:

    1)执行文件中打印本文件中的__name__,结果显示__main__;执行文件中import导入其它文件,其它文件中有个打印__name__ 即print(__name__),在本文件中显示那个文件的文件名,即模块名字。

    2)因此,当本程序执行中存在if __name__ == '__main__':,由于等式成立,所以if判断下的程序会执行。而导入的其它文件中存在if __name__ == '__main__':,由于在本文件导入时,__name__ 不等于 '__main__':,而是等于那个的模块名,所有其它文件中的if __name__ == '__main__':下的内容不会执行

    3)由此可知:如果想让本文件中的程序在其它文件导入时执行,就不要写在if __name__ == '__main__':下;反之,如果想让本文件中的程序在其它文件导入时不执行,就写在if __name__ == '__main__':下;

    4)在以上基础上:在Windows中,在文件中开启一个子进程以导入的方式在子进程内存空间中执行一遍。如果开启进程的命令没有写在if __name__ == '__main__':下,那么开启一个子进程后,子进程再开启一个子进程,这样就会出现递归创建子进程,所有会报错;

    如果是在Linux或者mac操作系统下,是fork创建进程,不需要写if __name__ == '__main__':也不会报错。创建进程的方式不同,他们是将父进程内存中的数据拷贝一份子进程中。

  • print([__name__])
    if __name__ == '__main__':
        # 控制当这个py文件被当作脚本直接执行的时候,就执行这里面的代码
        # 当这个py文件被当作模块导入的时候,就不执行这里面的代码
        print('hello hello')
    # __name__ == '__main__'
        # 执行的文件就是__name__所在的文件
    # __name__ == '文件名'
        # __name__所在的文件被导入执行的时候
    

主进程和子进程之间的关系

# 主进程和子进程之间的关系
import os
import time
from multiprocessing import Process
def func():
    print('start',os.getpid())
    time.sleep(10)
    print('end',os.getpid())

if __name__ == '__main__':
    p = Process(target=func)
    p.start()   # 异步 调用开启进程的方法 但是并不等待这个进程真的开启
    print('main :',os.getpid())
-------------------结果:
main : 6636
start 6752
end 6752

    # 主进程没结束 :等待子进程结束
    # 主进程负责回收子进程的资源
    # 如果子进程执行结束,父进程没有回收资源,那么这个子进程会变成一个僵尸进程

    # 主进程的结束逻辑
        # 主进程的代码结束
        # 所有的子进程结束
        # 给子进程回收资源
        # 主进程结束


import os
import time
from multiprocessing import Process
def func():
    print('start',os.getpid())
    time.sleep(1)
    print('end',os.getpid())
print("mcw")
if __name__ == '__main__':
    p = Process(target=func)
    p.start()   # 异步 调用开启进程的方法 但是并不等待这个进程真的开启
print('main :',os.getpid())
----------------结果:
mcw
main : 5244
mcw
main : 3392
start 3392
end 3392
#1)创建一个进程对象,并开启进程。对子进程的创建开启都是在父进程程序中执行的。子进程首先将父进程所有的执行程序导入并执行一次,因为if __name__ == '__main__':在子进程中不满足条件,所以没有执行。
2)在父进程的程序中先打印"mcw",创建好进程后,再打印“main”此处打印父进程pid。子进程导入这个程序的内容,先打印"mcw",再打印“main”此处打印子进程pid,因程序是在子进程中运行。然后将父进程中创建子进程时传进去的函数执行一下。打印start ,end。创建对象,对象.start()相当于给函数名加个括号,让函数执行。因为子进程的内存空间中已经有func函数了,所以进程对象.start(),在这个空间中找到func函数并执行。
3)疑问?创建的子进程是先导入内容,再执行传进来的这个函数吗?

主进程怎么知道子进程结束了的呢?

#[1] 主进程怎么知道子进程结束了的呢?
    # 基于网络、文件
# [2]join方法 :阻塞,直到子进程结束就结束
import time
from multiprocessing import Process
def send_mail():
    time.sleep(3)
    print('发送了一封邮件')
if __name__ == '__main__':
    p = Process(target=send_mail)
    p.start()   # 异步 非阻塞
    # time.sleep(5)
    print('join start')
    p.join()    #[3] 同步 阻塞 直到p对应的进程结束之后才结束阻塞
    print('5000封邮件已发送完毕')
-----------------结果:  
join start
发送了一封邮件
5000封邮件已发送完毕 
#[4]如果没有对象.join,由于没有阻塞,父进程和子进程是异步执行,而且创建进程花时间长些。在程序打印'join start'之后,会先打印'5000封邮件已发送完毕',由于程序中遇到对象.join(),所以会阻塞,等待子进程结束,父进程才继续执行这个程序。
# 开启5个进程,给公司的5000个人发邮件,发送完邮件之后,打印一个消息“5000封邮件已发送完毕”
import time
import random
from multiprocessing import Process
def send_mail(a):   
    time.sleep(random.random())
    print('发送了一封邮件',a)

if __name__ == '__main__':
    l = []
    for i in range(5):
        p = Process(target=send_mail,args=(i,))
        p.start()
        l.append(p)
    print(l)
    for p in l:p.join()  #[1]join起到阻塞,将之前的异步非阻塞变成同步阻塞 
    # 阻塞 直到上面的五个进程都结束
    print('5000封邮件已发送完毕')
-----------------------结果:
[<Process(Process-1, started)>, <Process(Process-2, started)>, <Process(Process-3, started)>, <Process(Process-4, started)>, <Process(Process-5, started)>]
发送了一封邮件 3
发送了一封邮件 4
发送了一封邮件 0
发送了一封邮件 1
发送了一封邮件 2
5000封邮件已发送完毕

分解分析上面if __name__ == '__main__':后面的 代码:

需求应为:同时发送多个邮件,直到所有邮件发送完毕才发送'5000封邮件已发送完毕'
(1)情况一:
创建五个进程,没有join阻塞。还没发邮件就已经打印发送完毕,有问题。
if __name__ == '__main__':
    for i in range(5):
        p = Process(target=send_mail,args=(i,))
        p.start()
    print('5000封邮件已发送完毕')
 ------------结果:
 5000封邮件已发送完毕
发送了一封邮件 0
发送了一封邮件 1
发送了一封邮件 3
发送了一封邮件 2
发送了一封邮件 4
(2)情况二:
因为每个都join阻塞,发送五次邮件,每次都是上一封邮件发送完毕才能发送下一封邮件,串行 效率低,不是想要的需求。
if __name__ == '__main__':
    for i in range(5):
        p = Process(target=send_mail,args=(i,))
        p.start()
        p.join()
    print('5000封邮件已发送完毕')
-------------结果:
发送了一封邮件 0
发送了一封邮件 1
发送了一封邮件 2
发送了一封邮件 3
发送了一封邮件 4
5000封邮件已发送完毕
(3)情况三:
并发执行五个进程,但是只阻塞最后一个了。因为for循环五次之后p代表最后一个,p.join()只阻塞最后一个。所以,打印5000封邮件已发送完毕  每次都是最后创建的那个进程运行完毕才打印,但是其它进程如果有比较慢才执行完毕的,那么就是还没有发送完所有邮件,就已经打印发送所有邮件完毕。这样有问题。
if __name__ == '__main__':
    for i in range(5):
        p = Process(target=send_mail,args=(i,))
        p.start()
    p.join()
    print('5000封邮件已发送完毕')
-------------结果:
发送了一封邮件 2
发送了一封邮件 1
发送了一封邮件 4
5000封邮件已发送完毕
发送了一封邮件 3
发送了一封邮件 0
(4)情况四:
[1]将每次创建并开启进程的进程对象追加到列表,循环列表对每一个进程执行 对象.join()阻塞。
[2]对已经结束的p.join就相当于pass,没影响。
[3]没有结束的就会阻塞住,主程序会等待没有结束的子进程结束。子进程是并发执行,并且每个都有阻塞
[4]完成的阻塞一下没问题,没完成的阻塞一下,所有阻塞结束,程序往下执行,即完成所有子进程结束,父进程重新从阻塞后面开始执行。所有子进程结束时间总长是以最长子进程时间为所有子进程结束时间的
假设运行时间是: 2 4 1 3 2
 所有子进程结束所需时间4s,时间花费是最大时长的那个子s进程所花费的时长。
if __name__ == '__main__':
    l = []
    for i in range(5):
        p = Process(target=send_mail,args=(i,))
        p.start()
        l.append(p)
    for p in l:p.join()  #join起到阻塞,将之前的异步非阻塞变成同步阻塞
    # 阻塞 直到上面的五个进程都结束
    print('5000封邮件已发送完毕')

#综上:想要实现并发并且还要等到所有的子进程结束之后,父进程才结束。那么就将每个进程追加到列表,循环列表对每个列表元素进行阻塞。

本程序中的创建子进程对象,也可以是传进导入的其它模块中的函数

xx.py----------------------
import os,time
def func():
    print('start',os.getpid())
    time.sleep(1)
    print('end',os.getpid())
test.py------------------
import os
import xx
from multiprocessing import Process
if __name__ == '__main__':
    p = Process(target=xx.func) #此处可以是导入的函数,也可以用模块.函数执行,估计类中方法也是可以的。
    p.start()   # 
    print('main :',os.getpid())
-----------------------test.py打印结果:
main : 5768
start 5896
end 5896

往子进程中传参(思考,传参字典等特殊传参呢?):

from multiprocessing import Process
def func(arg1,arg2):
    print(arg1,arg2)
if __name__ == '__main__':
    p = Process(target=func,args=(1,2))
    p.start()
-------------结果:
1 2
#给进程要执行的函数传参,在创建进程对象的时候添加args=(1,)。假如是单个参数,那么加个逗号才是元组。元组内元素与形参位置一一对应
from multiprocessing import Process
def func(arg1):
    print(arg1)
if __name__ == '__main__':
    p = Process(target=func,args=(1,))
    p.start()
------------结果:
1

传参总结:
[1]给进程要执行的函数传参,在创建进程对象的时候添加args=(1,)。假如是单个参数,那么加个逗号才是元组。元组内元素与形参位置一一对应
# 总结
# 1.开启一个进程
    # 函数名(参数1,参数2)
    # from multiprocessing import Process
    # p = Process(target=函数名,args=(参数1,参数2))
    # p.start()
   	#注意:函数名可以是从其它模块导入过来的。
# 2.父进程  和 子进程
# 3.父进程会等待着所有的子进程结束之后才结束
    # 为了回收资源
# 4.进程开启的过程中windows和 linux/ios之间的区别
    # 开启进程的过程需要放在if __name__ == '__main__'下
        # windows中 相当于在子进程中把主进程文件又从头到尾执行了一遍
            # 除了放在if __name__ == '__main__'下的代码
        # linux中 不执行代码,直接执行调用的func函数
# 5.join方法
    # 把一个进程的结束事件封装成一个join方法
    # 执行join方法的效果就是 阻塞直到这个子进程执行结束就结束阻塞

    # 在多个子进程中使用join
    # p_l= []
    # for i in range(10):
        # p = Process(target=函数名,args=(参数1,参数2))
        # p.start()
        # p_l.append(p)
    # for p in p_l:p.join()
    # 所有的子进程都结束之后要执行的代码写在这里
    
    
#注意:[1]发起创建子进程,子进程不是立即执行,有一点点延迟,并且父进程和子进程是异步的,数据等资源互相隔离,执行互不干扰。
[2]父子各自打印这个func地址是不一样的,表示子进程中的资源是独立存在的。是隔离开来的。
[3]主进程程序执行完后,主进程还没结束,在等待子进程结束;主进程要负责回收子进程的资源;如果子进程执行结束,父进程没有回收资源,那么这个子进程会成为一个僵尸进程
[4]主进程结束逻辑:

守护进程:

# 有一个参数可以把一个子进程设置为一个守护进程
import time
from multiprocessing import Process
def son1(a,b):
    while True:
        print('shou hu')
        time.sleep(0.5)
def son2():
    for i in range(5):
        print('qi ta jin cheng')
        time.sleep(1)
if __name__ == '__main__':
    p = Process(target=son1,args=(1,2)) 
    p.daemon = True ##一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
    p.start()      # 把p子进程设置成了一个守护进程
    p2 = Process(target=son2)
    p2.start()
    print("================")
    time.sleep(2)
    print("-----------------------") ##只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了
-----------------结果:
================
qi ta jin cheng
shou hu
shou hu
qi ta jin cheng
shou hu
shou hu
-----------------------
qi ta jin cheng
qi ta jin cheng
qi ta jin cheng

#守护进程创建:[1]对象.daemon=True变守护进程
			[2]一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
# 守护进程是随着主进程的代码结束而结束的
    # 生产者消费者模型的时候
    # 和守护线程做对比的时候
# 所有的子进程都必须在主进程结束之前结束,由主进程来负责回收资源

关于守护进程需要强调两点:
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就ok了,如果子进程的任务在主进程任务结束后就没有存在的必要了,那么该子进程应该在开启前就被设置成守护进程。主进程代码运行结束,守护进程随即终止;

使用场景:随着主进程的代码结束而结束就用守护进程

进程其它方法is_alive() terminate():

import time
from multiprocessing import Process

def son1():
    while True:
        print('is alive')
        time.sleep(0.5)

if __name__ == '__main__':
    p = Process(target=son1)
    p.start()      # 异步 非阻塞
    print(p.is_alive())
    time.sleep(1)
    p.terminate()   # 异步的 非阻塞
    print(p.is_alive())   # 进程还活着 因为操作系统还没来得及关闭进程
    time.sleep(0.01)
    print(p.is_alive())   # 操作系统已经响应了我们要关闭进程的需求,再去检测的时候,得到的结果是进程已经结束了
--------------结果:
True
is alive
is alive
True
False

# 什么是异步非阻塞?
    # terminate
[1]查看进程是否运行状态  对象.is_alive
[2]终止进程操作 对象.终止(terminate())   终止之后判断是否还活着,结果是还活着一段时间,即终止也是需要时间终止的
终止进程操作 对象.终止   终止之后判断是否还活着,结果是还活着一段时间。异步,不等他  非阻塞
start异步非阻塞

面向对象创建进程

import os
import time
from multiprocessing import Process

class MyProcecss2(Process):
    def run(self):
        while True:
            print('is alive')
            time.sleep(0.5)

class MyProcecss1(Process):
    def __init__(self,x,y):
        self.x = x
        self.y = y
        super().__init__()
    def run(self):
        print(self.x,self.y,os.getpid())
        for i in range(5):
            print('in son2')
            time.sleep(1)

if __name__ == '__main__':
    mp = MyProcecss1(1,2)
    mp.daemon = True
    mp.start()
    print(mp.is_alive())
    mp.terminate()
    mp2 = MyProcecss2()
    mp2.start()
    print('main :',os.getpid())
    time.sleep(1)
----------------结果:
True
main : 5808
is alive
is alive

面向对象编程简写:

class MyProcecss1(Process):  #
    def run(self):
        for i in range(5):
            print('jincheng1')
            time.sleep(1)
if __name__ == '__main__':
    mp = MyProcecss1()  
    mp.start()
----------------结果:
jincheng1
jincheng1
jincheng1
jincheng1
jincheng1
class MyProcecss1(Process): #1)创建一个类,继承Process。
    def __init__(self,x,y): #4)创建init初始化方法,往里面传参,然后run里面就可以使用传进去的参数了。怎么传进去?创建实例的时候传入参数。
        self.x = x
        self.y = y
        super().__init__()#5)如果子进程不需要传参,init方法可以不写。因为父类中init中有很多创建进程的步骤自定义没有,无法创建进程,所以super().无法创建时报错如下结果:
    def run(self): #2)必须写一个run方法,run方法里面写子进程的运行代码
        for i in range(5):
            print('jincheng%s',self.x,self.y)
            time.sleep(1)
if __name__ == '__main__':
    mp = MyProcecss1(1,2)   # #3)创建这个类的对象,往对象中传参实例变量。不能往run方法里面传参,因为没有调用run,是start开启进程类中封装了的运行的run。那么可以将参数放到init
    mp.start()
-----------------结果:
# assert self._popen is None, 'cannot start a process twice'
#AttributeError: 'MyProcecss1' object has no attribute '_popen'
jincheng%s 1 2
jincheng%s 1 2
jincheng%s 1 2
jincheng%s 1 2
jincheng%s 1 2

1557833734424

面向对象创建进程。
1)创建一个类,继承Process。
2)必须写一个run方法,run方法里面写子进程的运行代码
3)创建这个类的对象,往对象中传参实例变量。不能忘run方法里面传参,因为没有调用run,是start开启进程类中封装了的运行的run。那么可以放到init
4)创建init初始化方法,往里面传参,然后run里面就可以使用传进去的参数了。怎么传进去?创建实例的时候传入参数。
5)如果子进程不需要传参,init方法可以不写。因为父类中init中有很多创建进程的步骤自定义没有,无法创建进程,所以super()

同步阻塞 比如p.join 等待子进程结束父进程才往下执行

# Process类总结:
# 开启进程的方式
    # 面向函数
        # def 函数名:要在子进程中执行的代码
        # p = Process(target= 函数名,args=(参数1,))
    # 面向对象
        # class 类名(Process):
            # def __init__(self,参数1,参数2):   # 如果子进程不需要参数可以不写
                # self.a = 参数1
                # self.b = 参数2
                # super().__init__()
            # def run(self):
                # 要在子进程中执行的代码
        # p = 类名(参数1,参数2)
    # Process提供的操作进程的方法
        # p.start() 开启进程      异步非阻塞
        # p.terminate() 结束进程  异步非阻塞

        # p.join()     同步阻塞
        # p.isalive()  获取当前进程的状态

        # daemon = True 设置为守护进程,守护进程永远在主进程的代码结束之后自动结束

进程直接通信:

并发编程 买票系统 锁的概念:

并发进程做什么事。
并发编程结合网络编程

一:并发编程结合网络编程
1)并发编程创建子进程。子进程有自己的独立空间
2)网络编程tcp编程,多个客户端连接服务端,一个客户端在服务端收发信息的部分就创建一个进程,这样每个客户端对应服务端一个进程,实现多个客户端连接服务端。

二:车票购票系统:
购票多个人买票,一个一个排队购买,多人同时购买(创建子进程,实现并发)
用户太多,一台服务器处理并发子进程数量有限,再来一台服务器响应处理。多台服务器同时响应请求就要出现调度的服务器,负载均衡服务器。
买票信息存在数据库。没台处理服务器一个数据库,信息不一致。公用一台数据库

处理服务器,处理一条买票信息,数据库就要减少一个票,二者通信,网络通信。通信有延迟。
处理服务器和数据库的通信:
10个人抢一张票

  • 车票购票系统分析:

    1)查票一个函数

    2)10个人抢就是10个进程来执行这个函数,谁查询的解决,谁要传参进去,传参 args,传的是元组

    3)买票了:买票一个函数 time模拟网络延迟

    4)这时,1张票结果多个人都买到了,这时是有问题的,

    5)问题分析,因为多个人是有并发的,一个人读取到一张票,网络延迟,写入减少一张票需要0.1秒,在0.1秒内多个人又来并发读到一张票,做同样的操作。于是多个人在0.1秒内成功执行了操作.一张票多人都读取到买到了

    6)多个人来数据库读取请求。有个锁,只有一个人成功执行之后才能下一个人进来操作。只有一个进程对同一个文件操作完之后别的进程才能进来操作这个文件,保证数据一致性

    7)lock.acquire()加锁,没有抢到锁的进程被阻塞了,等待释放锁。代码前加锁,一个进程执行完释放锁,suo.释放()

    8)查票不加锁,买票必须加锁,不加锁数据会出错,买票是串行,非并行。
    所以:
    1、如果在一个并发的场景下涉及到某部分内容时需要修改一些所有进程共享的数据资源 需要加锁来维护数据的安全
    2、在数据安全的基础上,才考虑效率问题。
    这里查票函数并发,买票函数涉及到修改共享数据所以需要加锁串行,保证数据一致性。并发是多进程实现,一个网络连接(网络编程)创建一个子进程。
    3、同步存在的意义:

    10)进程与进程间数据隔离,任何对共享数据资源修改都存在数据安全问题,加锁解决

    11)加锁还可以上下文管理,用with封装了异常处理的,不会出现release不能解锁的问题 release是放在finamly的
    12)with加锁可以放在买票函数里,也可以放在task任务里。在task任务里,加锁然后执行买票函数

    13)在子进程中 对需要加锁的代码 进行with lock:

    14)查票函数 买票函数 任务函数(查看不需加锁 并发,加锁买票 串行 目的 保证数据一致性,不会混乱(如果修改共享数据也并发,每个人都读到一张票,然后做买票,再修改共享数据减去一张票,就是多个人买到票了))

# 并发 能够做的事儿
# 1.实现能够响应多个client端的server
# 2.抢票系统

#'ticket_count.txt'文本内容:
{"count": 0}

import time
import json
from multiprocessing import Process,Lock

def search_ticket(user):
    with open('ticket_count.txt') as f:
        dic = json.load(f)
        print('%s查询结果  : %s张余票'%(user,dic['count']))

def buy_ticket(user,lock):
    # with lock:
    # lock.acquire()   # 给这段代码加上一把锁
        time.sleep(0.02)
        with open('ticket_count.txt') as f:
            dic = json.load(f)
        if dic['count'] > 0:
            print('%s买到票了'%(user))
            dic['count'] -= 1
        else:
            print('%s没买到票' % (user))
        time.sleep(0.02)
        with open('ticket_count.txt','w') as f:
            json.dump(dic,f)
    # lock.release()   # 给这段代码解锁

def task(user, lock):
    search_ticket(user)
    with lock:
        buy_ticket(user, lock)

if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        p = Process(target=task,args=('user%s'%i,lock))
        p.start()
 -------------结果:
 user0查询结果  : 3张余票
user0买到票了
user1查询结果  : 3张余票
user1买到票了
user3查询结果  : 1张余票
user3买到票了
user6查询结果  : 0张余票

--------------------------
# 1.如果在一个并发的场景下,涉及到某部分内容
    # 是需要修改一些所有进程共享数据资源
    # 需要加锁来维护数据的安全
# 2.在数据安全的基础上,才考虑效率问题
# 3.同步存在的意义
    # 数据的安全性

# 在主进程中实例化 lock = Lock()
# 把这把锁传递给子进程
# 在子进程中 对需要加锁的代码 进行 with lock:
    # with lock相当于lock.acquire()和lock.release()
# 在进程中需要加锁的场景
    # 共享的数据资源(文件、数据库)
    # 对资源进行修改、删除操作
# 加锁之后能够保证数据的安全性 但是也降低了程序的执行效率


加锁的场景:
加锁的优缺点:
加锁方法:
加锁的原因:
什么是锁:
多把锁:

锁的简单理解:

场景:修改共享数据资源
方法:
从多进程模块导入Lock类
实例化类
with lock:
	函数执行(传参lock进入)
	def 函数(lock)不需要操作对lock
	
lock = Lock()
with lock:
	需要加锁解锁的函数执行,将锁对象传进去

进程之间的数据隔离*

from multiprocessing import Process
n = 100
def func():
    global n
    n -= 1

if __name__ == '__main__':
    p_l = []
    for i in range(10):
        p = Process(target=func)
        p.start()
        p_l.append(p)
    for p in p_l:p.join()
    print(n)
-------------结果:
100

队列的使用:

  • 两个隔离的进程间进行通信:
    一个父进程中多个子进程,一个子进程一个任务。子进程完成任务进程间需要通信。假设是计算求和,就需要返回计算结果。
    进程间通信方式: 队列 文件 管道 (数据表似乎也是文件)

  • 队列:

    1)导入模块

    2)创建队列,

    3)队列传到函数

    4)函数内调用,将数据put

    5)外 get

  • 队列原理:

1)相当于第三个文件,在内存中的

2)子进程put 父进程get

3)先进先出

  • 队列:基于
    文件家族的sockct实现,不是基于网络的。存取数据基于piclkle,lock
    两个进程往同一个队列里面存取数据,有可能两个进程往同一个地方存取到数据,所有要保证数据安全,队列加锁了,串行

  • 队列 sockct piclkle,lock

    管道 sockct piclkle

    队列基于管道+锁实现

    管道基于sockct piclkle实现

    (重写队列方法,会报错缺少东西,说明内部使用了)

  • 管道 :一个发一个收 效率高,多个的话那么数据不安全,它本身没有锁,所有效率高

    队列:多个收发

  • q.get在队列为空时发生阻塞。这样就会等待有值再往下执行
    创建队列传一个参数 为队列大小 。 q = Queue(5)
    put多个,满了阻塞
    put_nowait 当队列为满的时候再向队列中放数据 会报错并且会丢失数据
    get_nowait # 在队列为空的时候 直接报错

    get 阻塞 get_nowait非阻塞

  • 三个方法不准确,有隐患
    原因:在并发中获取三个值的路途中就已经被别的进程修改了,
    比如:获取的到是空的,值在返回的途中,有个进程已经放入值,这时空的结果就是有问题的。

    q.empty

    q.qsize

    q.full

    初始化里面初始化方法

from multiprocessing import Queue,Process
# 先进先出
def func(exp,q):
    ret = eval(exp)
    q.put({ret,2,3})
    q.put(ret*2)
    q.put(ret*4)

if __name__ == '__main__':
    q = Queue()
    Process(target=func,args=('1+2+3',q)).start()
    print(q.get())
    print(q.get())
    print(q.get())
-----------------结果:
{2, 3, 6}
12
24

# Queue基于 天生就是数据安全的
    # 文件家族的socket pickle lock
# pipe 管道(不安全的) = 文件家族的socket pickle
# 队列 = 管道 + 锁
# from multiprocessing import Pipe
# pip = Pipe()
# pip.send()
# pip.recv()
import queue
from multiprocessing import Queue
q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)   # 当队列为满的时候再向队列中放数据 队列会阻塞
print('5555555')
try:
    q.put_nowait(6)  # 当队列为满的时候再向队列中放数据 会报错并且会丢失数据
except queue.Full:
    pass
print('6666666')
--------------结果:
5555555
6666666import queue
from multiprocessing import Queue
q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)   # 当队列为满的时候再向队列中放数据 队列会阻塞
print('5555555')
try:
    q.put_nowait(6)  # 当队列为满的时候再向队列中放数据 会报错并且会丢失数据
except queue.Full:
    pass
print('6666666')

print(q.get())
print(q.get())
print(q.get())   # 在队列为空的时候会发生阻塞
print(q.get())   # 在队列为空的时候会发生阻塞
print(q.get())   # 在队列为空的时候会发生阻塞
try:
    print(q.get_nowait())   # 在队列为空的时候 直接报错
except queue.Empty:pass
-----------------结果:
5555555
6666666
1
2
3
4
5
from multiprocessing import Queue 
q = Queue(5) #从多进程导入Queue 队列, Queue(n)队列创建一个进程队列对象。最大存放n个数据
q.put("mcw") #put将数据放入进程队列
q.put("xiao")
aa=q.get() #get 将数据拿出进程队列 mcw 先进先出
bb=q.get() #xiao
print(aa)
print(bb)
----------------结果:
mcw
xiao
posted @ 2019-05-14 22:51  马昌伟  阅读(168)  评论(0编辑  收藏  举报