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
  • 相关阅读:
    UltraEdit语法高亮解决办法
    tcpdump命令格式及使用
    VS tricks
    git ready
    [FirefoxPlugin]Print pages to Pdf
    Searching and Navigating Code in VS 2010 (VS 2010 and .NET 4.0 Series)
    How to avoid StepInto unnecessary code area?
    Search and Navigation Tips/Tricks with Visual Studio
    squashing commits with rebase
    【原创】钻石继承与虚继承
  • 原文地址:https://www.cnblogs.com/weixinsb/p/13215341.html
Copyright © 2011-2022 走看看