zoukankan      html  css  js  c++  java
  • python——多线程,多进程,协程

    线程,进程

    定义:

    进程: 是对各种资源管理的集合,qq 要以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等

    线程: 是操作系统最小的调度单位, 是一串指令的集合。

    进程要想操作CPU,就必须要创建一个线程(进程中至少包含一个线程)

    区别:

    1.线程共享内存空间(共享数据等),进程的内存空间是独立的

    2.同一进程的线程之间可以相互交流 ,2个进程之间的交流必须通过一个中间代理

    3.线程可以操作和控制其他线程(同一进程下),进程只能操作和控制子进程。

    对主线程的更改可能会影响到其他线程的工作,对父进程的更改(除非关闭)不会影响子进程。(子进程还可以派生子进程)

    Python中的多线程 

    import threading
    
    def run(n):
      print('运行线程',n)
    
    for i in range(10):     # 创建10个线程
        t = threading.Thread(target=run, args=(i,))    # 线程运行的函数和参数
        t.setDaemon(True)   # 设置为守护线程(在主线程线程结束后自动退出,默认为False即主线程线程结束后子线程仍在执行)
        t.start()   # 启动线程

    上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。

    更多方法:

    • start            线程准备就绪,等待CPU调度
    • setName      为线程设置名称
    • getName      获取线程名称
    • setDaemon   设置为后台线程或前台线程(默认)
                           如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
                           如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
    • join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
    • run              线程被cpu调度后自动执行线程对象的run方法

    互斥锁(Lock、RLock)

    由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。

    import threading
    import time
    
    gl_num = 0
    
    lock = threading.RLock()    # 定义线程锁
    
    
    def Func():
        lock.acquire()      # 开始锁
        global gl_num
        gl_num += 1
        time.sleep(1)
        print(gl_num)
        lock.release()      # 结束锁
    
    
    for i in range(10):
        t = threading.Thread(target=Func)
        t.start()
        
    """
    没有锁的时候打印:
    >> 10,10,10,10,10,10,10,10,10,10
    
    有锁的时候打印:
    >> 1,2,3,4,5,6,7,8,9,10
    """
    

    信号量(Semaphore)

    互斥锁同时只允许一个线程更改数据,而信号量锁是同时允许一定数量的线程更改数据 ,多个线程同时执行完毕。

    import threading, time
    
    
    def run(n):
        semaphore.acquire()     # 信号量锁开始
        time.sleep(1)
        print("当前运行线程为: %s" % n)
        semaphore.release()     # 结束
    
    
    if __name__ == '__main__':
    
        num = 0
        semaphore = threading.BoundedSemaphore(5)    # 最多允许5个线程同时运行(5个5个一起出来)
        for i in range(20):
            t = threading.Thread(target=run, args=(i,))
            t.start()
    

    事件(event)

    python线程的事件用于主线程控制其他线程的执行,事件主要提供了两个方法:event.set()设定,event.clear()没设定。

    • event.wait():等待设定
    • event.is_set():判断是否设定
     1 import time,threading
     2 
     3 event = threading.Event()
     4 
     5 def lighter():
     6     count=0
     7     event.set()     #设定
     8     while True:
     9         if count<10 and count>=5:
    10             event.clear()   #清除设定
    11             print("33[41;1m红灯33[0m")
    12             time.sleep(1)
    13         elif count>10:
    14             count=0
    15             event.set()
    16         else:
    17             print("33[42;1m绿灯33[0m")
    18             time.sleep(1)
    19         count+=1
    20 
    21 def car(name):
    22     while True:
    23         if event.is_set():  #判断是否设定
    24             print("33[32;1m[%s] run...33[0m"%name)
    25             time.sleep(1)
    26         else:
    27             print('33[31;1m [%s] stop...'%name)
    28             event.wait()    #等待设定
    29             print('33[33;1m [%s]走咯'%name)
    30 
    31 
    32 light=threading.Thread(target=lighter,)
    33 
    34 light.start()
    35 
    36 car1=threading.Thread(target=car,args=('Tesla',))
    37 
    38 car1.start()
    红绿灯

    Python中的多进程 

    多进程特点:

    • 每一个进程都是由父进程启动的
    • 子进程被父进程启动后就是独立的(父进程copy了一份给子进程)
    from multiprocessing import Process
    
    def foo(i):
        print('say hi', i)
    
    if __name__ == '__main__':
        for i in range(10):
            p = Process(target=foo, args=(i,))
            p.start()

    获取进程id:

     1 from multiprocessing import Process     #多进程
     2 import os
     3 
     4 def info(title):
     5     print(title)
     6     print('module name:', __name__)
     7     print('parent process:', os.getppid())  #获取父进程的端口号
     8     print('process id:', os.getpid())   #获取当前进程的端口号
     9     print('
    ')
    10 
    11 def f(name):
    12     info('33[31;1mcalled from child process function f33[0m')
    13     print('hello', name)
    14 
    15 if __name__ == '__main__':
    16     info('33[32;1mmain process line33[0m')
    17     p = Process(target=f, args=('bob',))
    18     p.start()
    19     p.join()
    20 
    21 get进程id
    View Code

    进程数据共享

    • 进程各自持有一份数据,默认无法共享数据
    •  当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值。

    通过队列共享数据:

    from multiprocessing import Process, Queue
    
    def run(qq):
        qq.put("123")
    
    
    if __name__=='__main__':
        q=Queue()    #生成一个队列,通过队列进行传递数据
        p=Process(target=run,args=(q,))
        p.start()
        print(q.get())
        p.join()

    通过字典共享数据:

    from multiprocessing import Process, Manager
    import os
    def f(d, l):
        d[os.getpid()] =os.getpid()
        l.append(os.getpid())
        print(l)
    
    if __name__ == '__main__':
        with Manager() as manager:  #Manager()=manager
            d = manager.dict() #{} #生成一个字典,可在多个进程间共享和传递
    
            l = manager.list(range(5))#生成一个列表,可在多个进程间共享和传递
            p_list = []
            for i in range(10):
                p = Process(target=f, args=(d, l))
                p.start()
                p_list.append(p)
            for res in p_list: #等待结果
                res.join()
    
            print(d)
            print(l)
    

    进程锁:

    在多个进程共享一个屏幕时可能会导致输出的数据变乱。

    from multiprocessing import Process, Lock
    
    def f(l, i):
        l.acquire()     #设置进程锁
        try:
            print('hello world', i)
        finally:
            l.release()     #取消进程锁
    
    if __name__ == '__main__':
        l = Lock()  #实例化锁
        for num in range(10):
            Process(target=f, args=(l, num)).start()
    

    进程池:

    进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

    进程池中有两个方法:

    • apply:串行
    • apply_async:并行

     1 from  multiprocessing import Pool
     2 import time
     3 import os
     4 
     5 def Foo(i):
     6     time.sleep(2)
     7     print("in process",os.getpid())
     8     return i + 100
     9 
    10 def Bar(arg):
    11     print('-->exec done:', arg,os.getpid())
    12 
    13 if __name__ == '__main__':
    14     #freeze_support()
    15     pool = Pool(processes=3) #允许进程池同时放入5个进程
    16     print("主进程",os.getpid())
    17     for i in range(10):
    18         pool.apply_async(func=Foo, args=(i,), callback=Bar) #callback=回调
    19         #pool.apply(func=Foo, args=(i,)) #串行
    20         #pool.apply_async(func=Foo, args=(i,)) #并行
    21     print('end')
    22     pool.close()
    23     pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。.join()
    View Code

    协程(微线程)

    通过单线程实现并发(协程只有一个线程,so不用锁)

    协程的好处:

    • 无需线程上下文切换的开销
    • 无需原子操作锁定及同步的开销
    • "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
    • 方便切换控制流,简化编程模型
    • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

    缺点:

    • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
    • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

    使用yield实现协程操作例子

     1 import time
     2 
     3 def consumer(name):
     4     print("--->starting eating baozi...")
     5     while True:
     6         new_baozi = yield
     7         print("[%s] is eating baozi %s" % (name, new_baozi))
     8         # time.sleep(1)
     9 
    10 def producer():
    11     r = con.__next__()
    12     r = con2.__next__()
    13     n = 0
    14     while n < 5:
    15         n += 1
    16         con.send(n)
    17         con2.send(n)
    18         time.sleep(1)
    19         print("33[32;1m[producer]33[0m is making baozi %s" % n)
    20 
    21 if __name__ == '__main__':
    22     con = consumer("c1")
    23     con2 = consumer("c2")
    24     p = producer()
    25 
    26 yield
    View Code

    同步与异步性能差别

     1 import gevent
     2 
     3 def task(pid):
     4     gevent.sleep(0.5)
     5     print('Task %s done' % pid)
     6 
     7 def synchronous():  #每个都要等0.5s,需要5s
     8     for i in range(1, 10):
     9         task(i)
    10 
    11 def asynchronous():  #一共等0.5s
    12     threads = [gevent.spawn(task, i) for i in range(10)]
    13     gevent.joinall(threads)
    14 
    15 print('Synchronous:')
    16 synchronous()
    17 
    18 print('Asynchronous:')
    19 asynchronous()
    20 
    21 同步与异步
    View Code

    总结:

    1、多进程,多线程,协程

    操作系统方面的多进程,多线程,协程:

    操作系统可以开多个进程,一个进程可以有多个线程,多个线程可以被分配到不同的核心上跑,但实际上每个核心上只有一个线程,只是这个线程在不停的进行上下文的切换,给我们一种并发的感觉。

    协程:单线程的调度机制。它的作用是让原来要使用异步+回调方式(调用线程)写的非人类代码,可以用看似同步的方式写出来。协程是先出现的,但它有明显的时间差,没有并发的感觉,所以出现了线程。

    python的多进程,多线程,协程:

    但python的多线程只能在一个核心上跑(创始人没想到会有多核出现),就是单核的上下文切换,所以很鸡肋。于是协程在python大展拳脚,好多框架都是使用协程来解决多任务的,而不是线程(scrapy,tornado)。

    python中多进程,多线程,协程的使用:

    IO密集型:多线程/协程(可以用异步),cpu占用率低,单个cpu核心就够了

    CPU密集型:多进程,多给它几个核心提升性能

    2、python多线程不用join,进程需要(否则子进程会在进程结束时强制被关闭)

    1 python 默认参数创建线程后,不管主线程是否执行完毕,都会等待子线程执行完毕才一起退出,有无join结果一样

    2 如果创建线程,并且设置了daemon为true,即thread.setDaemon(True), 则主线程执行完毕后自动退出,不会等待子线程的执行结果。而且随着主线程退出,子线程也消亡。

    3 join方法的作用是阻塞,等待子线程结束,join方法有一个参数是timeout,即如果主线程等待timeout,子线程还没有结束,则主线程强制结束子线程。

    4 如果线程daemon属性为False, 则join里的timeout参数无效。主线程会一直等待子线程结束。

    5 如果线程daemon属性为True, 则join里的timeout参数是有效的, 主线程会等待timeout时间后,结束子线程。此处有一个坑,即如果同时有N个子线程join(timeout),那么实际上主线程会等待的超时时间最长为 N * timeout, 因为每个子线程的超时开始时刻是上一个子线程超时结束的时刻。

  • 相关阅读:
    windows api学习笔记读写其他进程的内存
    WindowsApi学习笔记创建一个简单的窗口
    windows api学习笔记创建进程
    汇编语言基础教程加法指令
    windows api学习笔记使用定时器
    windows api学习笔记多线程
    C#中两个问号的双目运算符
    通过UDP的组播方式收发数据
    windows api学习笔记用临界区对象使线程同步
    工作流学习笔记ifElse活动;从工作流中取出返回值;计算器实例
  • 原文地址:https://www.cnblogs.com/x54256/p/7684106.html
Copyright © 2011-2022 走看看