精华内容
下载资源
问答
  • 最近在学python的网络编程,学了socket通信,并利用socket实现了一个具有用户验证功能,可以上传下载文件、可以实现命令行功能,创建和删除文件夹,可以实现的断点续传等功能的FTP服务器。但在这当中,发现一些概念...
  • Python并发编程

    千次阅读 2018-07-08 19:11:00
    基于消息传递的并发编程是大势所趋,通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求, 还可以扩展到分布式系统中。 multiprocessing给每个进程赋予单独的Python解释器,这样就规避了全局...

    + 协程

    协程,Coroutine,是一种用户态的轻量级线程。

    协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方(非CPU),在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,CPU感觉不到协程的存在,协程是用户自己控制的。

    协程实现原理:利用一个线程,分解一个线程成为多个“微线程”,属于程序级别的划分。

    协程的优点:

        无需线程上下文切换的开销 
        无需数据操作锁定及同步的开销 
        方便切换控制流,简化编程模型 

        高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理

    缺点:

        无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上,协程如果要使用多核CPU的话,那么就需要先启多个进程,在每个进程下启一个线程,然后在线程下在启协程。

        日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。 

        python通过yield提供对协程的基本支持,但是不完全,第三方库gevent为协程提供了比较完善的支持。

     

      从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数。可是,在协程中, yield 通常出现在表达式的右边(例如, datum = yield),可以产出值,也可以不产出 —— 如果 yield 关键字后面没有表达式,那么生成器产出 None。

    参考网址:https://blog.csdn.net/andybegin/article/details/77884645

    协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是next(…) 函数。

    def simple_coroutine():
        print('-> start')
        x = yield
        print('-> recived', x)
    
    sc = simple_coroutine()
    
    next(sc)
    sc.send('zhexiao')
    

    1. 协程使用生成器函数定义:定义体中有 yield 关键字。 
    2. yield 在表达式中使用;如果协程只需从客户那里接收数据,那么产出的值是 None —— 这个值是隐式指定的,因为 yield 关键字右边没有表达式。 
    3. 首先要调用 next(…) 函数,因为生成器还没启动,没在 yield 语句处暂停,所以一开始无法发送数据。 
    4. 调用send方法,把值传给 yield 的变量,然后协程恢复,继续执行下面的代码,直到运行到下一个 yield 表达式,或者终止。

    ==注意:send方法只有当协程处于 GEN_SUSPENDED 状态下时才会运作,所以我们使用 next() 方法激活协程到 yield 表达式处停止,或者我们也可以使用 sc.send(None),效果与 next(sc) 一样==。

    协程可以身处四个状态中的一个。当前状态可以使用inspect.getgeneratorstate(…) 函数确定,该函数会返回下述字符串中的一个: 
    1. GEN_CREATED:等待开始执行 
    2. GEN_RUNNING:解释器正在执行 
    3. GEN_SUSPENED:在yield表达式处暂停 
    4. GEN_CLOSED:执行结束

    import time
    def consumer(name):
        print('--->staring eating baozi....')
        while True:
            new_baozi = yield
            print("%s is eating baozi %s" %(name,new_baozi))
    
    def producer():
        r = con.__next__()
        r = con2.__next__()
        n = 0
        while n<5:
            n += 1
            print("chenkaifang is making baozi %s" %n)
            con.send(n)
            con2.send(n)
        
    
    if __name__ == '__main__':
        con = consumer("c1")
        con2 = consumer("c2")
        p = producer()
    

    yield from高级用法....

     

    greenlet有已经封装好的协程:

    from greenlet import greenlet
    def test1():
        print(12)
        gr2.switch()
        print(34)
        gr2.switch()
    
    def test2():
        print(56)
        gr1.switch()
        print(78)
    
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()

     

    参考网址:https://www.cnblogs.com/zhaof/p/7536569.html

    Gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。它让开发者在不改变编程习惯的同时,用同步的方式写异步I/O的代码。它的特点是协程的自动切换(遇到IO操作自动轮训切换)。

    使用Gevent的性能确实要比用传统的线程高,甚至高很多。但这里不得不说它的一个坑,实际使用中要慎用Gevent:

    1. Monkey-patching,我们都叫猴子补丁,因为如果使用了这个补丁,Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。但是我们无法保证你在复杂的生产环境中有哪些地方使用这些标准库会由于打了补丁而出现奇怪的问题
    2. 第三方库支持。得确保项目中用到其他用到的网络库也必须使用纯Python或者明确说明支持Gevent
    3. Greenlet不支持Jython和IronPython,这样就无法把gevent设计成一个标准库了

    gevent是第三方库,通过greenlet实现协程,其基本思想是:当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成。

        gevent使用实例1:

    from gevent import monkey;monkey.patch_socket()
    import gevent
    
    def f(n):
        for i in range(n):
            print(gevent.getcurrent(),i)
            gevent.sleep(0)#让出CPU使用权
            
    g1 = gevent.spawn(f,5)
    g2 = gevent.spawn(f,5)
    g3 = gevent.spawn(f,5)
    g1.join()
    g2.join()
    g3.join()

        gevent I/O操作实例1:

    from gevent import monkey;monkey.patch_all()
    import gevent
    import urllib.request
    
    def f(url):
        print("GET:%s" %url)
        resp = urllib.request.urlopen(url)
        data = resp.read()
        print("%d bytes received form %s" %(len(data),url))
    gevent.joinall([
        gevent.spawn(f,"https://www.python.org/"),
        gevent.spawn(f,"https://www.yahoo.com/"),
        gevent.spawn(f,"https://github.com/"),
        ])

     

    + 多线程

    由于GIL的存在,即使硬件有N个核,也只能利用一个核。

    ThreadLocal类型处理变量共享与绑定,与Java类似。

    threading.Thread类的使用:

     1,在自己的线程类的__init__里调用threading.Thread.__init__(self, name = threadname)  Threadname为线程的名字

     2, run(),通常需要重写,编写代码实现做需要的功能。

     3,getName(),获得线程对象名称

     4,setName(),设置线程对象名称

     5,start(),启动线程

     6,jion([timeout]),等待另一线程结束后再运行。

     7,setDaemon(bool),设置子线程是否随主线程一起结束,必须在start()之前调用。默认为False。

     8,isDaemon(),判断线程是否随主线程一起结束。

     9,isAlive(),检查线程是否在运行中。

    参考网址:

    https://www.cnblogs.com/chengd/articles/7770898.html

    https://www.cnblogs.com/semiok/articles/2640929.html

    #Lock不允许同一线程多次acquire
    import time
    import threading
    
    balance = 0
    lock = threading.Lock()
    
    def change(n):
        global balance
        balance = balance + n
        balance = balance - n
    
    def run_thread(n):
        for i in range(10000):
            lock.acquire()
            try:
                change(n)
            finally:
                lock.release()
    t1 = threading.Thread(target = run_thread,args=(5,))
    t2 = threading.Thread(target = run_thread,args=(8,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(balance)

    Rlock允许同一线程多次acquire
    threading.Condition,高级锁,内部维护一个锁对象,提供wait、notify、notifAll方法(必须在获得锁后才能调用)
    threading.Semaphore BoundedSemaphore   信号量同步

    thread.Event:事件处理机制

    Queue:同步队列

    threading.active_count    threading.current_thread   threading.enumerate   threading.get_ident   threading.main_thread

     

    线程同步的4种方式:锁、信号量、条件变量、同步队列

    + 多进程

    Windows下python用multiprocessing实现多进程,multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上,一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。

    参考网址:https://blog.csdn.net/cityzenoldwang/article/details/78584175

    import multiprocessing
    import os
    
    def run_proc(name):
        print('Child process {0} {1} Running '.format(name, os.getpid()))
    
    if __name__ == '__main__':
        print('Parent process {0} is Running'.format(os.getpid()))
        for i in range(5):
            p = multiprocessing.Process(target=run_proc, args=(str(i),))
            print('process start')
            p.start()
        p.join()
        print('Process close')
    

    Pool 可以提供指定数量的进程供用户使用,默认是 CPU 核数。当有新的请求提交到 Poll 的时候,如果池子没有满,会创建一个进程来执行,否则就会让该请求等待。 

    apply_async 方法用来同步执行进程,允许多个进程同时进入池子,apply只能允许一个进程进入池子,在一个进程结束之后,另外一个进程才可以进入池子。

    进程间通信Queue、Pipe、Socket。基于消息传递的并发编程是大势所趋,通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求, 还可以扩展到分布式系统中。

    multiprocessing给每个进程赋予单独的Python解释器,这样就规避了全局解释锁所带来的问题。

     

    + 异步IO

    通常,我们写服务器处理模型的程序时,有以下几种模型:
    (1)每收到一个请求,创建一个新的进程,来处理该请求;
    (2)每收到一个请求,创建一个新的线程,来处理该请求;
    (3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求(就是以事件驱动的方式来处理)
    上面的几种方式,各有千秋,
    第(1)种方法,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。
    第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。
    第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。
    综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式。

    对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

    1. 等待数据准备 (Waiting for the data to be ready)(就是将数据放到内核缓存中)
    2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

    基于这两步,Linux有5种网络模型:阻塞、非阻塞、I/O多路复用、信号驱动、异步IO。

    IO multiplexing就是我们说的select,poll,epoll。I/O多路复用的特点是通过一种机制 一个进程能同时等待多个文件描述符,而这些文件描述符(socket连接)其中的任意一个进入读就绪状态,select()函数就可以返回。epoll同样只告知那些就绪的文件描述符,epoll采用基于事件的就绪通知方式,epool会告诉用户进程具体哪个socket连接有数据了,所以用户进程不需要在将所有socket 连接全都循环一次才发现具体哪个有数据。epoll解决C10K问题,采用mmap减少复制开销,epoll技术的编程模型就是异步非阻塞回调,也可以叫做Reactor、事件驱动、事件轮循(EventLoop)、libevent、Tornado、Node.js这些就是epoll时代的产物。

    协程本质上也是异步非阻塞技术,它是将事件回调进行了包装,让程序员看不到里面的事件循环。进程/线程是操作系统充当了EventLoop调度,而协程是自己用epoll进行调度。

     

    asyncio在python3.4被引入标准库,Python 3.5添加了async和await这两个关键字,分别用来替换asyncio.coroutineyield from,协程成为新的语法,而不再是一种生成器类型了。

    首先需要明确一点,asyncio使用单线程、单个进程的方式切换;现存的一些库其实并不能原生的支持asyncio(因为会发生阻塞或者功能不可用),比如requests,如果要写爬虫,配合asyncio的应该用aiohttp,其他的如数据库驱动等各种Python对应的库也都得使用对应的aioXXX版本了。

     

    asyncio模块包含多种同步机制:信号量、锁、条件变量、事件、队列

    import asyncio
    
    async def hello1():
        print("1,Hello World!")
        r = await asyncio.sleep(1) 
        print("1,Hello again!")
        for i in range(5):
            print(i)
    
    async def hello2():
        print("2,Hello World!")
        print("2,Hello again!")
        for i in range(5):
            print(i)
    
    coros = [hello1(),hello2()]
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(*coros))
    

     

    + 同步、异步、阻塞、非阻塞

    同步:就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。

    异步:当一个异步过程调用发出后,调用者不会立刻得到结果。通过状态、通知来通知调用者,或通过回调函数处理这个调用。

    单进程的异步编程模型称为协程。

    阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。

    非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

    同步阻塞:效率最低,什么都不能干;异步阻塞;同步非阻塞也效率低;异步非阻塞效率高。

     

    + 进程、线程、协程应用场景

    IO密集型:用多线程+gevent(更好), 多线程 (抛弃Gevent,用asyncio)

    计算密集型:用多进程

    如果要启动大量的子进程,可以用进程池的方式批量创建子进程。ThreadPoolExecutor和ProcessPoolExecutor

     

    如果分不清任务是CPU密集型还是IO密集型,就用下面两个方法试,哪个快用哪个:

    from multiprocessing import Pool
    from multiprocessing.dummy import Pool
    如果一个任务拿不准是CPU密集还是I/O密集型,且没有其它不能选择多进程方式的因素,都统一直接上多进程模式。

     

    1. aiohttp。一个实现了PEP3156的HTTP的服务器,且包含客户端相关功能。最早出现,应该最知名。
    2. sanic。后起之秀,基于Flask语法的异步Web框架。
    3. uvloop。用Cython编写的、用来替代asyncio事件循环。作者说「它在速度上至少比Node.js、gevent以及其它任何Python异步框架快2倍」。
    4. ujson。比标准库json及其社区版的simplejson都要快的JSON编解码库。
    展开全文
  • python 并发编程 最初发布于 melvinkoh.me 在本文中,我将首先带您了解并发编程与并行执行之间的区别,讨论有关Python内置的并发编程机制以及Python中多线程的陷阱。 了解并发编程与并行执行 尽管事实上这两个...

    python 并发编程

    最初发布于 melvinkoh.me

    在本文中,我将首先带您了解并发编程与并行执行之间的区别,讨论有关Python内置的并发编程机制以及Python中多线程的陷阱。

    了解并发编程与并行执行

    尽管事实上这两个术语经常互换使用,但并发编程并不等同于并行执行。

    >没有并行的并发插图

    并发是一个属性,可以同时运行多个操作,但这并不意味着可以并发。 (想象一下,如果您的处理器是单线程的。)

    >并行性插图

    并行是实际上同时运行操作的属性。 通常由硬件限制决定。

    将您的程序视为一条快餐链,当构建两个单独的订单和收款柜台时,并发即被合并。 但是,它不能确保并行性,因为它取决于可用雇员的数量。 如果只有一名员工同时处理订单和收款请求,则这些操作不能并行运行。 仅当有两个员工同时服务于订单和收货时,才出现并行。

    Python内置

    现在,我们在Python中拥有什么? Python是否有内置程序可以帮助我们构建并发程序并使它们并行运行?

    在下面的讨论中,我们假定我们的程序都是在多线程或多核处理器中编写并运行的。

    答案是Jein (德语为是和否)。 为什么是? Python确实具有用于最常见的并发编程结构(多处理和多线程)的内置库。 您可能会认为,由于Python同时支持,为什么选择Jein? 原因是,由于Python中的GIL,Python中的多线程并不是真正的多线程。

    多线程—基于线程的并行性

    threading是提供用于创建和管理线程的API的软件包。 Python中的线程始终是不确定性的,它们的调度由操作系统执行。 但是,多线程可能无法达到您的预期。

    为什么Python中的多线程可能不是您想要的?

    除了常见的陷阱(如死锁)之外,多线程通常会出现饥饿。 Python因其在多线程中的性能较差而臭名昭著。

    让我们看一下以下代码片段:

    import threading
    
    def countdown () :
         x = 1000000000
         while x > 0 :
               x -= 1
    # Implementation 1: Multi-threading
    def implementation_1 () :
         thread_1 = threading.Thread(target=countdown)
         thread_2 = threading.Thread(target=countdown)
         thread_1.start()
         thread_2.start()
         thread_1.join()
         thread_2.join()
    # Implementation 2: Run in serial
    def implementation_2 () :
         countdown()
         countdown()

    哪个实施会更快? 让我们执行一个定时。

    >两种实现的时序结果

    令人惊讶的是,连续运行2个countdown()胜过多线程吗? 这怎么可能呢? 多亏了臭名昭著的Global Interpreter Lock(GIL)。

    什么是全局翻译锁定(GIL)?

    在大多数情况下,取决于Python的分布是CPython的实现。 CPython是Python的原始实现,您可以在此StackOverflow线程中阅读有关它的更多信息。

    在CPython中,通过引入称为全局解释器锁(又称为GIL)的互斥对象来支持多线程。 这是为了防止多个线程同时访问同一Python对象。 这是有道理的,您不希望其他人在处理对象时对其进行变异。

    >实施示意图_1

    因此,从上面的代码段中, implementation_1创建2个线程,并应在多线程系统上并行运行。 但是,一次只能有一个线程可以容纳GIL,一个线程必须等待另一线程释放GIL才能运行。 同时,由操作系统完成的调度和切换会带来开销,从而使implementation_1变得更加缓慢。

    如何绕过GIL?

    我们如何在保持多线程使用的同时绕过GIL? 这个问题没有通用的好答案,因为这与您的代码目的有所不同。

    可以选择使用Jython,PyPy或IronPython等不同的Python实现。 我个人不主张使用不同的Python实现,因为大多数编写的库都没有针对不同的Python实现进行测试。

    另一个可能的解决方法是使用C扩展,或更好地称为Cython 。 请注意,Cython和CPython是不同的。 您可以在此处阅读有关Cython的更多信息。

    请改用多处理。 由于在多处理中,将为每个子进程创建一个解释器。 不存在争用GIL简单线程的情况,因为在每个进程中始终只有一个主线程。

    尽管存在所有陷阱,我们是否仍应使用多线程?

    如果您的任务是受I / O约束的,则意味着线程将花费大部分时间来处理I / O,例如执行网络请求。 仍然可以使用多线程,因为在大多数情况下,线程被操作系统阻塞并放入阻塞队列中。 线程也总是比进程少耗费资源。

    多处理—基于过程的并行性

    让我们使用多重处理实现我们之前的代码片段。

    import multiprocessing
    # countdown() is defined in the previous snippet.
    def implementation_3 () :
         process_1 = multiprocessing.Process(target=countdown)
         process_2 = multiprocessing.Process(target=countdown)
         process_1.start()
         process_2.start()
         process_1.join()
         process_2.join()

    结果本身是不言自明的。

    >多处理与多线程的计时结果

    结论

    GIL的约束在最初作为Python开发人员时吸引了我。 直到我确定时机,我才意识到使用线程的决定是毫无价值的。 希望本文对您有所帮助。

    翻译自: https://hackernoon.com/concurrent-programming-in-python-is-not-what-you-think-it-is-b6439c3f3e6a

    python 并发编程

    展开全文
  • 某 课 网Python高级编程和异步IO并发编程,高级进阶技巧,内容全面,值得分享
  • 大家好,并发编程 今天开始进入第二篇。 今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础。学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章。 本文目录 学会使用函数...
  • 对于并发编程Python的实现,总结了一下,大致有如下三种方法: 多线程 多进程 协程(生成器) 在之后的章节里,将陆陆续续地给大家介绍到这三个知识点。 . 并发编程的基本概念 在开始讲解理论知识之前,先过...
  • 主要介绍了python并发编程多进程 模拟抢票实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主进程创建子进程目的是:主进程有一个任务需要并发执行,那开启子进程帮我并发执行任务 主进程创建子进程,然后将该进程设置成守护自己的进程 关于守护进程需要强调两点: 其一:守护进程会在主进程代码执行结束后...
  • 主要介绍了python 并发编程 阻塞IO模型原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍官网链接: 练习一:练习二:三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化...
  • 1、传统的编程模式例如:线性模式大致流程开始--->代码块A--->代码块B--->代码块C--->代码块D--->......--->结束每一个代码块里是完成各种各样事情的代码,但编程者知道代码块A,B,C,D...的执行...
  • #并发运行,效率高,但竞争同一打印终端,带来了打印错乱 from multiprocessing import Process import time def task(name): print(%s 1 % name) time.sleep(1) print(%s 2 % name) time.sleep(1)
  • python并发编程的理解,包括并发编程事件驱动模型、异步编程、IO模式等,
  • 主要介绍了python并发编程 Process对象的其他属性方法join方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了python 并发编程 多路复用IO模型详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了python 并发编程 非阻塞IO模型原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 这一章中,我们跳转到协调多个计算机和处理器的...并发(或并行)引入了新的挑战,并且我们会开发新的机制来管理并发程序的复杂性。分布式系统是自主的计算机网络,计算机互相通信来完成一个目标。分布式系统中的计算机都
  • 然而 Python 中由于 Global Interpreter Lock (全局解释锁 GIL )的存在,每个线程在在执行时需要获取到这个 GIL ,在同一时刻中只有一个线程得到解释锁的执行, Python 中的线程并没有真正意义上的并发执行,多...
  • Python并行编程手册.pdf

    2019-07-03 08:38:09
    本章最后将会对Python编程语言进行简短的介绍。 这门语言 的诸多特性(比如易于使用和学习, 可扩展性以及丰富的软件库与应用) 使得它对于任何应用来说都是一个颇有价值 的工具, 当然, 对于并行计算亦如此。 本...
  • 15、python并发编程

    千次阅读 2021-02-18 23:30:10
    15、python并发编程 多道技术 进程理论 开启进程的两种方式 进程对象的join方法 进程之间数据相互隔离 进程对象的其他方法 僵尸进程与孤儿进程 守护进程与互斥锁 进程通信IPC机制 生产者消费模型 一、多道技术 多...

    15、python并发编程

    • 多道技术
    • 进程理论
    • 开启进程的两种方式
    • 进程对象的join方法
    • 进程之间数据相互隔离
    • 进程对象的其他方法
    • 僵尸进程与孤儿进程
    • 守护进程与互斥锁
    • 进程通信IPC机制
    • 生产者消费模型

    一、多道技术

    多道技术的思想: 利用单核实现并发的效果

    这里我们要清楚2个概念,什么是并行什么是并发

    • 并发

      ​ 看起来像同时运行的就可以称之为并发

    • 并行

      ​ 真正意义上的同时执行

    现在我们知道,并行肯定是并发,但是单核的计算机是肯定不能实现并行,却可以实现并发。

    在这里插入图片描述

    多道技术节省了多个程序运行的总耗时

    多道技术重点


    空间上的复用与时间上的复用

    • 空间上的复用

      ​ 多个程序公用一套计算机硬件

    • 时间上的复用

      • 如上图,单道完成10秒,而多道在执行A的时候在读取任务B,完成只需要6秒,类似数学中的统筹规划。

    多道中的CPU切换

    """
    切换cpu分为两种情况:
    	1.当一个程序遇到IO操作的时候,操作系统会剥夺程序的cpu执行权限
    	  作用:提高cpu利用率并也不影响程序的执行效率
    	2.当一个程序长时间占用cpu的时候,操作系统也会剥夺该程序的cpu执行权限
    	  即各个程序轮流使用cpu实现多个程序运行
    	  作用:降低了程序的执行效率(原时间+切换时间)
    """
    

    二、进程理论

    程序与进程的区别

    """
    程序就是一堆躺在硬盘上的代码,是“死”的
    进程是表示程序正在运行的过程,是“活”的
    """
    

    进程调度

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

    • 先来先服务调度算法

      """对长作业有利,对短作业无益"""
      
    • 短作业优先调度算法

      """对短作业有利,对长作业无益"""
      
    • 时间片轮转法+多级反馈队列

      在这里插入图片描述

    进程运行的三状态图

    在这里插入图片描述

    两对重要概念

    • 同步与异步
    """描述的是任务的提交方式"""
    同步:任务提交后,原地等待任务的返回结果,任务完成后返回结果再去做其他事情。
        
    异步:任务提交后,不原地等待任务的返回结果,直接去做其他事情,任务的返回结果会有一个异步回调机制自动处理
    
    • 阻塞与非阻塞
    """描述程序的运行状态"""
    阻塞:程序三状态中的阻塞态
    非阻塞:程序三状态中的就绪态与运行态
    
    理想状态:我们应该让我们的程序永远处于就绪态和运行态之间切换
    

    最高效的组合就是异步非阻塞。

    三、开启进程的两种方式

    第一种方式(常用)

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    from multiprocessing import Process
    import time
    
    
    def task(name):
    	print(f'{name} is running')
    	time.sleep(3)
    	print(f'{name} is over')
    
    
    """
    windows操作系统下一定要在main内创建
    因为windows下创建进程类似于模块导入的方式
    会从上往下依次执行代码
    
    linux中则是直接将代码完整的拷贝一份
    """
    if __name__ == '__main__':
    	# 1.创建一个进程对象
    	p = Process(target=task, args=('No.1',))
    	# 2.通过操作系统创建一个进程
    	p.start()
    	print('Master')
    
    

    运行结果

    Master
    No.1 is running
    No.1 is over
    

    第二种方式(了解)

    # 第二种方式:类的继承
    import time
    from multiprocessing import Process
    
    
    class MyProgress(Process):
    	def run(self):
    		print('task is running!')
    		time.sleep(2)
    		print('task is over!')
    
    
    if __name__ == '__main__':
    	p = MyProgress()
    	p.start()
    	print('Master')
    
    

    运行结果

    Master
    task is running!
    task is over!
    

    可以看到两种创建进程的方式都实现了异步,在p进程执行的过程中并没有影响到后续master的打印。

    总结

    """
    创建进程就是在内存中申请一块内存空间
    一个进程对应在内存中就是一块独立的内存空间
    多个进程对应在内存中就是多块独立的内存空间
    多个进程之间互不影响
    进程与进程之间默认情况下数据是无法直接交互,需要借助第三方工具、模块。
    """
    

    四、进程对象的join方法

    join方法是让主进程等待子进程代码运行结束后再继续运行,不影响其他子进程的执行。

    简单案例

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    import time
    from multiprocessing import Process
    
    
    def task(name, n):
    	print(f'{name} is running')
    	time.sleep(n)
    	print(f'{name} is over')
    
    
    if __name__ == '__main__':
    	p = Process(target=task, args=('No.1', 1))
    	p.start()
    	# 主进程等待子进程p运行结束后再继续运行
    	p.join()
    	print('Master')
    
    

    运行结果

    No.1 is running
    No.1 is over
    Master
    

    join方法类似于拥有vip进行插队

    五、进程之间数据相互隔离

    简单案例证明

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    from multiprocessing import Process
    
    data = 100
    
    
    def task():
    	global data
    	data = 666
    	print(f'task data : {data}')
    
    
    if __name__ == '__main__':
    	p = Process(target=task)
    	p.start()
    	p.join()
    	print(f'main data : {data}')
    

    运行结果

    task data : 666
    main data : 100
    

    在这里插入图片描述

    六、进程对象的其他方法

    pid

    计算机上运行着很多进程,计算机会给每一个运行的进程分配一个pid。

    """
    windows
    在cmd中输入tasklist查看pid
    tasklist | findstr pid查看pid对应进程
    
    linux
    在终端中输入ps aux查看pid
    ps aux | grep pid查看pid对应进程
    """
    

    进程相关常用方法

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    from multiprocessing import Process
    import os
    import time
    
    
    def task():
    	time.sleep(3)
    	print(f'子进程的pid:{os.getpid()}')
    	print(f'子进程的父进程的pid:{os.getppid()}')
    
    
    if __name__ == '__main__':
    	p = Process(target=task)
    	p.start()
    	p.terminate()   # 杀死当前进程
    	print(f'当前进程是否存活:{p.is_alive()}')
    	print(f'主进程的pid :{os.getpid()}')
    	print(f'主进程的父进程的pid :{os.getppid()}')
    
    

    运行结果

    当前进程是否存活:True
    主进程的pid :3432
    主进程的父进程的pid :7024
    

    七、僵尸进程与孤儿进程

    僵尸进程

    故名思意,就是死了还没死透。

    当你开设了子进程之后,该进程死后不会立刻释放占用的进程号。因为要让父进程能够看到它开设的子进程的基本信息,如pid,运行时间等。所有的进程都会步入僵尸进程。

    父进程等待子进程运行结束或者父进程调用join方法会回收子进程占用的pid号

    孤儿进程

    子进程存活,父进程意外死亡,此时该子进程就为孤儿进程。

    操作系统此时会开设一块区域专门回收孤儿进程的相关资源。

    八、守护进程与互斥锁

    守护进程

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    import time
    from multiprocessing import Process
    
    
    def task():
    	print('子进程正在存活')
    	time.sleep(5)
    	print('子进程正常死亡')
    
    
    if __name__ == '__main__':
    	p = Process(target=task)
    	p.daemon = True
    	p.start()
    	time.sleep(1)
    	print('主进程死亡')
    
    

    运行结果

    子进程正在存活
    主进程死亡
    

    可以看到我们将p设为守护进程后,p会随主进程死亡而一起死亡。这样的进程就是守护进程。而设置守护进程一定要在进程启动之前对其进行设置。

    互斥锁

    多个进程操作同一份数据的时候,会出现数据错乱的问题。针对该问题,解决方式就是加锁处理:将并发变为串行,牺牲效率但是保证了数据的安全

    互斥锁模拟抢票

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    import json
    import random
    import time
    from multiprocessing import Process, Lock
    
    
    # 查票
    def search(user):
    	with open('data', 'r', encoding='utf-8') as f:
    		dic = json.load(f)
    	print(f'用户{user}查询余票{dic.get("ticket")}')
    
    
    # 买票
    def buy(user):
    	with open('data', 'r', encoding='utf-8') as f:
    		dic = json.load(f)
    	# 模拟网络延迟
    	time.sleep(random.randint(1, 5))
    	if dic.get('ticket') > 0:
    		dic['ticket'] -= 1
    		with open('data', 'w', encoding='utf-8') as f:
    			json.dump(dic, f)
    		print(f'用户{user}买票成功')
    	else:
    		print('购票失败')
    
    
    def run(user, lock):
    	search(user)
    	lock.acquire()     # 加锁
    	buy(user)
    	lock.release()     # 解锁
    
    
    if __name__ == '__main__':
    	lock = Lock()
    	for i in range(1, 6):
    		p = Process(target=run, args=(i, lock))
    		p.start()
    
    

    data中的数据

    {"ticket": 1}
    

    运行结果

    用户1查询余票1
    用户2查询余票1
    用户3查询余票1
    用户5查询余票1
    用户4查询余票1
    用户1买票成功
    购票失败
    购票失败
    购票失败
    购票失败
    

    在上述实验中如果不加入互斥锁时,程序运行速度很快并且用户1-用户5都能购买到票,而票总共只有一张,这显然不合理。在加入互斥锁后程序运行效率大幅降低,但只有一个用户可以买到票,这就是我们牺牲了程序的效率而保证了数据的安全性

    注意:

    • 锁不要轻易使用,容易造成死锁现象
    • 锁只在处理数据的部分加以保证数据安全

    九、进程通信IPC机制

    我们知道进程与进程之间的数据是相互隔离的无法互相调用,但实际生产环境中我们却常需要在进程见进行相互通信,这里我们引入IPC机制(Intent Process Communication),意识就是进程间通信。注意IPC机制并不是只在编程语言中存在,它在操作系统中同样存在。而在python中我们可以通过管道队列两种方式来实现进程间的通信,实现原理为在进程间建立一个中转的空间,让进程与进程之间通过队列或管道进行数据的交互。

    管道

    """
    管道:subprocess模块
    stdin  stdout  stderr
    """
    

    队列

    """
    队列是在管道的基础上增加了锁等一系列的功能,所以我们在进程间的通信常用队列
    队列:先进先出
    堆栈:先进后出
    """
    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    from multiprocessing import Queue
    
    # 创建一个队列
    # 参数表示生成的队列最大可以同时存放的数据量 python3.7默认参数为2147483647
    q = Queue(3)
    # put方法往队列中存数据
    q.put('chaney')
    q.put('tony')
    q.put('Jerry')
    # get方法从队列中取数据
    q1 = q.get()
    q2 = q.get()
    q3 = q.get()
    # 可以看到队列的中数据遵循先进先出原则
    print(q1, q2, q3)
    
    

    运行结果

    chaney tony Jerry
    

    在put和get方法存取数据时,如果存数据超过队列最大数据量或取数据超出所有数据量,该方法会原地阻塞,程序会卡住。这里我们可以增加if判断或者异常捕获来解决程序的健壮性。

    在了解了队列的前置知识后我们来实现一个简单的IPC进程通信

    主进程与子进程借助队列通信

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    """
    主进程与子进程借助队列通信
    """
    from multiprocessing import Queue, Process
    
    
    def task(q):
    	# 通过get方法从队列中取到主进程数据
    	print(f'{q.get()} is running')
    
    
    if __name__ == '__main__':
    	q = Queue()     # 创建队列
    	p = Process(target=task, args=(q,))     # 开子进程
    	p.start()
    	q.put('task')     # 通过put方法将数据通过队列给到子进程
    
    
    

    运行结果

    task is running
    

    子进程与子进程借助队列通信

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    """
    子进程与子进程借助队列通信
    """
    from multiprocessing import Queue, Process
    
    
    def producer(q):
    	# 往队列中存数据
    	q.put('message from producer')
    
    
    def consumer(q):
    	# 取出队列中的数据
    	print(q.get())
    
    
    if __name__ == '__main__':
    	q = Queue()     # 创建队列
    	p1 = Process(target=producer, args=(q,))     
    	p2 = Process(target=consumer, args=(q,))
    	p1.start()
    	p2.start()
    
    

    运行结果

    message from producer
    

    在这里插入图片描述

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 75,817
精华内容 30,326
关键字:

python并发编程

python 订阅