zoukankan      html  css  js  c++  java
  • Python—进程、线程、协程

    Python—进程、线程、协程

     

    一、线程

      线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

    方法:

      start            线程准备就绪,等待CPU调度

      setName      设置线程名称

      getName      获取线程名称

      setDaemon   把一个主进程设置为Daemon线程后,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论有没执行完成,都会停止

      join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义  

      run              线程被cpu调度后自动执行线程对象的run方法

    threading模块

      线程的两种调用方式:

    1.直接调用(常用)

    复制代码
    import threading
    import time
    
    '''直接调用'''
    
    def hello(name):
        print("Hello %s"%name)
        time.sleep(3)
    
    if __name__ == "__main__":
        t1=threading.Thread(target=hello,args=("zhangsan",)) #生成线程实例
        t2=threading.Thread(target=hello,args=("lisi",))
    
        t1.setName("aaa")   #设置线程名
        t1.start()  #启动线程
        t2.start()
        t2.join()   #join  等待t2先执行完
        print("Hello")
        print(t1.getName()) #获取线程名
    复制代码

    2.继承式调用

    复制代码
    '''继承式调用'''
    import threading
    import time
    class MyThread(threading.Thread):
        def __init__(self,name):
            threading.Thread.__init__(self)
            self.name = name
        def run(self):
            print("Hello %s"%self.name)
            time.sleep(3)
    
    if __name__ == "__main__":
        t1=MyThread("zhangsan")
        t2=MyThread("lisi")
        t1.start()
        t2.start()
    复制代码

    setDaemon线程

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import time
    import threading
    def run(n):
        print('Hello..[%s]
    ' % n)
        time.sleep(2)
    
    def main():
        for i in range(5):
            t = threading.Thread(target=run,args=[i,])
            t.start()
            t.join(1)
    
    m = threading.Thread(target=main,args=[])
    m.setDaemon(True) #将主线程设置Daemon设置为True后,主线程执行完成时,其它子线程会同时退出,不管是否执行完任务
    m.start()
    print("--- done----")
    复制代码

    线程锁Lock

      一个进程下可以启动多个线程,多个线程共享父进程的内存空间,每个线程可以访问同一份数据,所以当多个线程同时要修改同一份数据时,就会出现错误

      例如:

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import threading
    import time
    
    num = 100 #设置一个共享变量
    def show():
        global num  #在函数内操作函数外变量,需设置为全局变量
        time.sleep(1)
        num -= 1
    list=[]
    for i in range(100):
        t = threading.Thread(target=show)
        t.start()
        list.append(t)
    
    for t in list:
        t.join()
    print(num)
    复制代码

      上面的例子在正常执行完成后的num的结果应该是0,但实际上每次的执行结果都不太一样,因为当多个线程同时要修改同一份数据时,就会出现一些错误(只有

    在python2.x运行才会出现错误,python3.x中不会),所以每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以加上线程锁

    来确保每次修改数据时只有一个线程在操作。

      加锁代码

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import threading
    import time
    
    num = 100 #设置一个共享变量
    lock=threading.Lock()  #生成全局锁
    def show():
        global num  #在函数内操作函数外变量,需设置为全局变量
        time.sleep(1)
        lock.acquire()  #修改前加锁
        num -= 1
        lock.release()  #修改后解锁
    list=[]
    for i in range(100):
        t = threading.Thread(target=show)
        t.start()
        list.append(t)
    
    for t in list:
        t.join()
    
    print(num)
    复制代码

    递归锁RLock

      就是在一个大锁中再包含子锁

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import threading
    #递归锁
    def run1():
        lock.acquire()  #小锁
        global num
        num +=1
        lock.release()
        return num
    def run2():
        lock.acquire()  #小锁
        global  num2
        num2+=1
        lock.release()
        return num2
    def run3():
        lock.acquire()  #大锁
        res = run1()
        res2 = run2()
        lock.release()
        print(res,res2)
    
    if __name__ == '__main__':
        num,num2 = 0,0
        lock = threading.RLock()    #生成Rlock
        for i in range(10):
            t = threading.Thread(target=run3)
            t.start()
    
    while threading.active_count() != 1:#如果不等于1,说明子线程还没执行完毕
        pass #打印进程数
    else:
        print(num,num2)
    复制代码

    Semaphore

      同时允许一定数量的线程更改数据

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import threading
    import time
    def run(n):
        semaphore.acquire()
        time.sleep(1)
        print("run the thread: %s" %n)
        semaphore.release()
    
    if __name__ == '__main__':
        semaphore  = threading.BoundedSemaphore(3) #设置最多允许3个线程同时运行
        for i in range(20):
            t = threading.Thread(target=run,args=(i,))
            t.start()
    while threading.active_count() != 1:
        pass
    else:
        print('----done---')
    复制代码

    event

      实现两个或多个线程间的交互,提供了三个方法 set、wait、clear,默认碰到event.wait 方法时就会阻塞。

      event.set(),设定后遇到wait不阻塞

      event.clear(),设定后遇到wait后阻塞

      event.isSet(),判断有没有被设定

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import threading
    def start():
        print("---start---1")
        event.wait()    #阻塞
        print("---start---2")
    
    if __name__ == "__main__":
        event = threading.Event()
        t = threading.Thread(target=start)
        t.start()
    
        result=input(">>:")
        if result == "set":
            event.set() #设定set,wait不阻塞
    复制代码

    二、进程

    multiprocessing模块

    进程调用

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from multiprocessing import Process
    import time
    def start(name):
        time.sleep(1)
        print('hello', name)
    
    if __name__ == '__main__':
        p = Process(target=start, args=('zhangsan',))
        p1 = Process(target=start, args=('lisi',))
        p.start()
        p1.start()
        p.join()
    复制代码

    进程间通讯

      每个进程都拥有自己的内存空间,因此不同进程间内存是不共享的,要想实现两个进程间的数据交换,有几种方法

    Queue(队列)

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from multiprocessing import Process, Queue
    def start(q):
        q.put( 'hello')
    
    if __name__ == '__main__':
        q = Queue()
        p = Process(target=start, args=(q,))
        p.start()
        print(q.get())
        p.join()
    复制代码

    Pipe(管道,不常用)

      把管道的两头分别赋给两个进程,实现两个进程的互相通信

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from multiprocessing import Process, Pipe
    
    def start(conn):
        conn.send('hello')#发送
        print(conn.recv())#接收
        conn.close()
    
    if __name__ == '__main__':
        parent_conn, child_conn = Pipe()    #生成一个管道
        p = Process(target=start, args=(child_conn,))
        p.start()
        print(parent_conn.recv())#接收
        parent_conn.send("11111")#发送
        p.join()
    复制代码

    Manager(实现了进程间真正的数据共享)

    复制代码
    #!/usr/bin/env python
    from multiprocessing import Process, Manager
    def f(dic, list,i):
        dic['1'] = 1
        dic['2'] = 2
        dic['3'] = 3
        list.append(i)
    
    if __name__ == '__main__':
        manager = Manager()
        dic = manager.dict()#通过manager生成一个字典
        list = manager.list(range(5))#通过manager生成一个列表
        p_list = []
        for i in range(10):
            p = Process(target=f, args=(dic, list,i))
            p.start()
            p_list.append(p)
        for res in p_list:
            res.join()
    
        print(dic)
        print(list)
    #执行结果
    '''
    {'2': 2, '3': 3, '1': 1}
    [0, 1, 2, 3, 4, 1, 9, 2, 5, 3, 7, 6, 0, 8, 4]
    '''
    复制代码

    进程池

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

    进程池中有两个方法:

    1、apply(同步)

    2、apply_async(异步)

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from  multiprocessing import Process,Pool
    import time
    
    def Foo(i):
        time.sleep(1)
        return i+100
    
    def Bar(arg):
        print('number::',arg)
    
    if __name__ == "__main__":
        pool = Pool(3)#定义一个进程池,里面有3个进程
        for i in range(10):
            pool.apply_async(func=Foo, args=(i,),callback=Bar)
            #pool.apply(func=Foo, args=(i,))
    
        pool.close()#关闭进程池
        pool.join()#进程池中进程执行完毕后再关闭,(必须先close在join)
    复制代码

    callback是回调函数,就是在执行完Foo方法后会自动执行Bar函数,并且自动把Foo函数的返回值作为参数传入Bar函数

    三、协程

      协程,又称微线程,是一种用户态的轻量级线程。协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置,当程序中存在大量不需要CPU的操作时(IO),适用于协程。

    协程有极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。

    不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

    因为协程是一个线程执行,所以想要利用多核CPU,最简单的方法是多进程+协程,这样既充分利用多核,又充分发挥协程的高效率。

    那符合什么条件就能称之为协程:1、必须在只有一个单线程里实现并发 2、修改共享数据不需加锁 3、用户程序里自己保存多个控制流的上下文栈 4、一个协程遇到IO操作自动切换到其它协程

    python中对于协程有两个模块,greenlet和gevent。

    Greenlet(greenlet的执行顺序需要我们手动控制)

    复制代码
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from greenlet import greenlet
    def test1():
        print (11)
        gr2.switch()    #手动切换
        print (22)
        gr2.switch()
    
    def test2():
        print (33)
        gr1.switch()
        print (44)
    
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()
    复制代码

    gevent(自动切换,由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成)

    复制代码
    from gevent import monkey; monkey.patch_all()
    import gevent
    import time
    
    
    def foo():
        print('11')
        time.sleep(3)
        print('22')
    
    def bar():
        print('33')
        print('44')
    
    gevent.joinall([
        gevent.spawn(foo),
        gevent.spawn(bar),
    ])
    复制代码

    运行结果:(从结果可以看出,它们是并发执行的)

    11
    33
    44
    22
  • 相关阅读:
    NBUT 1120 Reimu's Teleport (线段树)
    NBUT 1119 Patchouli's Books (STL应用)
    NBUT 1118 Marisa's Affair (排序统计,水)
    NBUT 1117 Kotiya's Incantation(字符输入处理)
    NBUT 1115 Cirno's Trick (水)
    NBUT 1114 Alice's Puppets(排序统计,水)
    188 Best Time to Buy and Sell Stock IV 买卖股票的最佳时机 IV
    187 Repeated DNA Sequences 重复的DNA序列
    179 Largest Number 把数组排成最大的数
    174 Dungeon Game 地下城游戏
  • 原文地址:https://www.cnblogs.com/weixinsb/p/13215341.html
Copyright © 2011-2022 走看看