zoukankan      html  css  js  c++  java
  • [python]python进阶编程(6)-多进程和多线程

    说明

    在某些情况下,需要通过多进程或者多线程来提高程序的运行效率,那么python中的多进程和多线程是如何实现的呢,今天来详细讨论一下。

    多进程

    类似于C语言,可以通过系统的fork来进行多进程的创建(只可在linux下运行),以下是多进程运行的示例:

    import	os
    pid = os.fork() # 父进程中返回值会是子进程PID值,子进程中返回值为0
    print('父子进程都会输出。') # 运行两次,分别是父进程和子进程
    if pid == 0: 
    	print('子进程的PID是%d,父进程的PID是%d'%(os.getpid(),os.getppid())) 
    else:  
    	print('父进程的PID是%d'%os.getpid()) #当父进程使用getpid的时候获得的是父进程的pid。
    

    同样地,进程的fork也可以通过运行多次进行嵌套,以达到实现进程的进程的目的。示例代码如下所示:

    import os
    pid = os.fork() #创建子进程,接收pid的返回值。
    if pid > 0: #判断是子进程还是父进程。
    	print('父进程') #当pid的返回值是0的时候,会运行父进程
    else:
    	print('子进程') #否则就是子进程
    pid =os.fork() #让之前的父子进程再次创建各自的子进程
    if pid > 0: #判断父子进程
    	print('父进程的子进程') #这里会运行2次父进程
    else:
    	print('子进程的子进程') #这里也会运行两次子进程
    

    Process库实现多进程

    在win上是默认不支持fork的,因此想要使用fork方法就必须通过其他的第三方库来实现:

    def child():
        while True:
            print("this is child process!")
            time.sleep(1)
    
    def test_process():
        p = Process(target=child)
        p.start()
        p.join(3) # 3秒后运行父进程
        print('this is father process')
    

    当然也可以通过自定义的方式来生成新的进程类,重写run方法以满足有不同需要的多进程。 示例代码如下:

    class MyProcess(Process):
        def run(self):
            while True:
                print("this is child process!")
                time.sleep(1)
    

    进程池Pool的用法

    进程池会创建n个进程,指定进程的个数,这样在计算是就可以得到固定数量的子进程:

    def child(num):
        for i in range(2):
            print('进程的pid是%d,进程值是%d'%(os.getpid(),num))
            time.sleep(1)
    def test_pool():
        p = Pool(2) # 进程池大小
        for i in range(6): 
            print('--%d--'%i)
            p.apply_async(child, (i,))
        
        p.close() # 关闭进程池
        p.join() 
    

    进程的Queue用法

    一个较为常见的问题为生产消费问题,在Process库中通过Queue来实现:

    def write(q): 
        for v in range(10):
            print('Put %s to Queue'%v)
            q.put(v) 
            time.sleep(0.5)
    
    def read(q):
        while not q.empty():
            v = q.get(True)
            print('Get %s from Queue'%v)
            time.sleep(1)
    
    def test_queue():
        q = Queue() # 可以声明队列的存储空间
        pw = Process(target=write, args=(q, ))
        pr = Process(target=read2, args=(q, ))
        pw.start() #开始执行pw。
        pr.start() #开始执行pr。
        pw.join() #等待pw结束
        pr.join() #等待pr结束
        print('Over')  #主进程结束
    

    当生产者和消费者的速度不一致时,就会出现等待态的情景。

    Pool中Queue的用法

    使用Pool的Queue相当于多生产者多消费者的模式,Queue中可以有多个进程的读写。

    def write(q, num): 
        for v in range(10):
            print('Put %s to Queue'%v)
            q.put(v) 
            print('write进程的pid是%d,进程值是%d'%(os.getpid(),num))
            time.sleep(0.5)
    
    def read(q, num):
        while not q.empty():
            v = q.get(True)
            print('Get %s from Queue'%v)
            print('read进程的pid是%d,进程值是%d'%(os.getpid(),num))
            time.sleep(1)
    
    def test_pool_queue():
        q = Manager().Queue(2) #这里实例化的时候是使用Manager的Queue
        p = Pool(10)
        for i in range(6):
            p.apply_async(write,(q,i)) #将任务加入Pool的进程池,注意这里的参数于Process不同。
            p.apply_async(read,(q,i)) #将任务加入Pool的进程池,注意这里的参数于Process不同。
        p.close() #关闭进程池,不再接收进程。
        p.join() #子进程完毕,运行以下的主进程。
        print('Over')
    

    多线程

    基本用法

    多线程的用法和多进程基本类似,只不过将Process转化为

    import threading
    from threading import Thread, Lock
    import time
    
    num = 0
    
    def work1():
        global num
        # time.sleep(0.9)
        for i in range(int(1e6)):
            num += 1
        print("work1 的num为 %d"%(num))
    
    def work2():
        global num
        # time.sleep(1)
        for i in range(int(1e7)):
            num += 1
        print("work2 的num为 %d"%(num))
    
    def test_thread():
        t = Thread(target=work1) # 创建第一个线程内置的self.name属性为Thread-1,并指向work1
        tt = Thread(target=work2) #创建第二个线程内置的self.name属性为Thread-2,并指向work2
        t.start() #开始执行
        tt.start() #开始执行
        time.sleep(2) #主线程休息2秒
        print('最后的num值是%d'%num) # 最后的num值是10263017
    

    为什么最终的结果不是11000000,而是10263017,是因为同时读写,当拿到全局变量num时,work1和work2同时对num增加操作,所以最终结果不符合预期。

    互斥锁

    一种解决上述冲突的方法是加入互斥锁,即每次对变量进行读写的时候加锁,等到写操作完成后就释放锁:

    def work1_lock(l):    
        global num
        l.acquire() # 加锁
        for i in range(int(1e6)):
            num += 1
        print("work1 的num为 %d"%(num))
        l.release()  # 释放锁
    
    def work2_lock(l):    
        global num
        l.acquire()
        for i in range(int(1e7)):
            num += 1
        print("work2 的num为 %d"%(num))
        l.release()
    def test_lock():
        print('最后的num值是%d'%num)
        l = Lock() #实例化互斥锁,互斥锁是为了保护子线程不争抢数据而使用的一个类。
        t = Thread(target=work1_lock, args=(l,))
        tt = Thread(target=work2_lock, args=(l,))
        t.start()
        tt.start()
        t.join()
        tt.join()
        print('最后的num值是%d'%num) # 11000000
    

    threadlocal

    threadlocal提供了一种线程内部使用工具的方法,在多个线程使用全局数据时,不可避免地出现锁的情况,而解决方法之一便是通过个线程之间各有自己私有的数据来解决,threadlocal虽然是一个全局量,但是能够为不同的线程提供私有数据的机会。以下是示例代码:

    def work1_local(l, name):
        l.name = name #将参数name传递给local实例对象的name属性。注意:这里的l.name是创建的对象属性。
        work2_local(l) #调用work函数
        print('work1: hello,%s,线程的name是%s'%(name,threading.current_thread().name))
    
    def work2_local(l,):
        name = l.name  # 获取的name将是每个线程之间互不影响
        print('work2: hello,%s,线程的name是%s'%(name,threading.current_thread().name))
    def test_local():
        l = threading.local()
        t1 = threading.Thread(target=work1_local,args=(l, '小李')) #实例化线程对象,并调用work,参数name是小李。
        t2 = threading.Thread(target=work1_local,args=(l, '小王'))#实例化线程对象,并调用work,参数name是小王。
        t1.start()
        t2.start()
        t1.join()
        t2.join()
    
  • 相关阅读:
    C++ 将对象写入文件 并读取
    IronPython fail to add reference to WebDriver.dll
    How to Capture and Decrypt Lync Server 2010 TLS Traffic Using Microsoft Tools
    .net code injection
    数学系学生应该知道的十个学术网站
    Difference Between Currency Swap and FX Swap
    Swift开源parser
    谈谈我对证券公司一些部门的理解(前、中、后台)[z]
    JDK8记FullGC时候Metaspace内存不会被垃圾回收
    JVM源码分析之JDK8下的僵尸(无法回收)类加载器[z]
  • 原文地址:https://www.cnblogs.com/wildkid1024/p/13233751.html
Copyright © 2011-2022 走看看