Python 多线程&多进程

进程与线程

  • 进程:一个任务成为一个进程(Process),比如打开浏览器就是启动一个浏览器进程、打开Tim就是启动Tim进程……
  • 线程:一个进程里面有很多个子任务,把子任务称为线程,比如打开浏览器看视频,音频与动画是两个线程……
  • 一个进程至少都有一个线程。
  • 真正的并行执行多任务只能再多核CPU上实现,由于任务数量一般都远大于CPU核心数量,所以一般都会自动把多任务轮流分配在每个核心上交替执行(不停地切换,看起来就是多任务执行)。

多线程

 threads = []    #线程列表
    for i in range(0,100):
        thread = threading.Thread(target=fun,args=(i,i+5),kwargs = {}) 
#args= () 传入默认参数(传入一个tuple只有一个数据的时候(a,)),kwargs ={}传入关键字参数
        threads.append(thread)
        thread.start()    #运行
        #创建一个jion()方法来堵塞,让所有线程执行才执行最后的Done
    for i in threads:
        i.join()
  • 在主线程中利用循环可以创建多个线程,利用thread.join()可以让线程堵塞,所以可以用一个list记录所有线程,在主线程的最后遍历一遍这个列表,对每一个thread执行thread.join().然后再在这个join循环之后写下要继续执行的东西即可。(一般打印Done之类的)

线程同步

  • 多线程中,所有变量都由所有的线程共享,一个变量可以被任何一个线程修改,在运行多线程的时候会改乱数据。
    可以利用Lock()来上锁,某个线程执行函数的时候该函数只能被该线程占有,别的线程不能执行,但是这样会牺牲效率。
    上锁方法——在要线程要执行的内容中加入:
lock = threading.Lock()
lock.acquire()    #获取锁
try:
    fun(n)    #执行某个函数
finally:
    lock.release()    #释放锁

全局解释器锁(GIL)

  • 在多线程操作的时候不能调用多个CPU内核,在CPU密集型的任务(解析视频、计算)不推荐用多线程而应该用多进程。
  • 多线程任务应用场景: IO密集型,即python爬虫的开发,由于绝大多数时间爬虫是在等待socket返回数据,网络IO的操作延时比CPU大得多。

多进程

  • Windows环境下的多进程(推荐在Unix/Linux环境下写,但是Windows也能写)
    from multiprocessing import Process
    os.getpid()可以拿到当前进程的id.(GetProcessID)
    os.getppid()可以拿到当前子进程的父进程的id (GetParentProcessID)
  • 进程的参数传递与线程一致。
  • Windows环境下IDE不能执行,要去命令行处才能执行。
from multiprocessing import Process
import os 


def test(name):
    print('child process %s,id is %s,his parent is %s' % (name,os.getpid(),os.getppid()))


if __name__ == '__main__':   
    ps = []    
    for i in range(3):
        p = Process(target=test,args=('haha',))
        print('parent is %s' % os.getpid())
        ps.append(p)
        p.start()
    for i in ps:
        i.join()
    print('end')

运行结果:

parent is 23796
parent is 23796
parent is 23796
child process haha,id is 24300,his parent is 23796
child process haha,id is 2296,his parent is 23796
child process haha,id is 3100,his parent is 23796
end

进程池(Pool)

  • 要启动大量子进程的话,可以用进程池的方式批量创建。进程池通过事先划分一块系统资源区域,这组资源区域在服务器启动时就已经创建和初始化,用户如果想创建新的进程,可以直接取得资源,从而避免了动态分配资源。
  • 进程数多的时候,手动操作进程很繁琐,用Pool()很方便。
  • 当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。
from multiprocessing import Pool
import os 
import time
import random


def long_time_task(name):
    print('Run task %s (%s)...parent id %s' % (name, os.getpid(),os.getppid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool()
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))  #分配一个进程池中的进程给函数用。
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')
#def test(name):

运行结果:

Parent process 17872.
Waiting for all subprocesses done...
Run task 0 (11256)...parent id 17872
Run task 1 (20664)...parent id 17872
Run task 2 (13540)...parent id 17872
Run task 3 (8312)...parent id 17872
Task 1 runs 0.31 seconds.
Run task 4 (20664)...parent id 17872
Task 2 runs 0.39 seconds.
Task 3 runs 1.25 seconds.
Task 0 runs 2.75 seconds.
Task 4 runs 2.52 seconds.
All subprocesses done.
  • Pool()对象调用join()会等待所有子进程执行完毕,调用必须先调用close(),调用close()之后就不能再添加新的Process了。
  • task 0,1,2,3 都是立刻执行,而task4比较慢,前面4次循环分配完之后进程池里面没有子进程留给下一个了,所以要等待,改成Pool(5)就可以一次执行5个了。

进程间通信

1.Queue
有两个方法 .put().get(),都具有两个可选参数:blocked(默认值为True,表明可以堵塞一段timeout时间来等待队列有剩余空间(.put())或是等待Queue中有数据.get()) 和 timeout(指定带等待使时间)

from multiprocessing import Process,Pool,Queue
import os ,time,random


# 写数据
def write(q):
    for value in ['A','B','C']:
        print('put {} to queue'.format(value))
        q.put(value)
        time.sleep(random.random())


#读数据
def read(q):
    while True:
        value = q.get()
        print('get {} from quete'.format(value))
    
    
if __name__ == '__main__':
    q = Queue()    #主进程创建Queue,作为参数传给子进程
    pw = Process(target = write , args=(q,))
    pr = Process(target = read , args=(q,))
    pw.start()
    pr.start()
    pw.join()
    pr.terminate()    # pr进程为死循环,不能等待其结束,只能强行终止。

运行结果:

put A to queue
get A from quete
put B to queue
get B from quete
put C to queue
get C from quete

2.Pipe(常用于两个进程间通信)

  • Pipe方法返回(conn1,conn2)代表一个管道的两端,Pipe方法有duplex参数,默认为True,代表两个管道均可收发信息。若为Flase 则代表conn1只能接收消息,conn2只能发送消息。
from multiprocessing import Process,Pool,Queue,Pipe
import os ,time,random


#发送端
def send(pipe):
    for i in ['a','b','c']:
        print('Process %s send:%s' % (os.getpid(),i))
        pipe.send(i)
        time.sleep(random.random())
        

#接受端
def recv(pipe):
    while True:
        print('Process %s receive:%s' %(os.getpid(),pipe.recv()))
        time.sleep(random.random())
        
    
if __name__ == "__main__":
    pipe = Pipe()    #创建Pipe对象,返回值为一个tuple.
    ps = Process(target = send, args = (pipe[0],))    #传入tuple的第一项作为发送
    pr = Process(target = recv, args = (pipe[1],))    #传入tuple的第二项作为接收
    ps.start()
    pr.start()
    ps.join()
    pr.terminate()

运行结果:

Process 22332 send:a
Process 1884 receive:a
Process 22332 send:b
Process 1884 receive:b
Process 22332 send:c

多线程与多进程

1.多进程的优点是稳定性高,一个子进程崩溃不会影响其他的进程。缺点在于创建进程的开销很大,而且操作系统能同时运行的进程数有限,系统调度会出现问题。
2.多线程比多进程快一点点,缺点在于一个子线程崩可能导致整个进程崩溃。Windows下,多线程比多进程效率要高。
3.无论多进程多线程都要由一定限度,数量一多效率肯定不高。(会消耗掉系统所有资源)
4.计算密集型——利用C语言编写好,Python的效率太低了。
IO密集型(网络、磁盘IO的任务)——利用python开发就好了,因为消耗CPU很少。利用C语言开发无法提高效率。

你可能感兴趣的:(Python 多线程&多进程)