python——多線程,多進程,協程

線程,進程

定義:python

進程: 是對各類資源管理的集合,qq 要以一個總體的形式暴露給操做系統管理,裏面包含對各類資源的調用,內存的管理,網絡接口的調用等算法

線程: 是操做系統最小的調度單位, 是一串指令的集合。編程

進程要想操做CPU,就必需要建立一個線程(進程中至少包含一個線程)網絡

區別:多線程

1.線程共享內存空間(共享數據等),進程的內存空間是獨立的併發

2.同一進程的線程之間能夠相互交流 ,2個進程之間的交流必須經過一箇中間代理app

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根據指定算法進行調度,分片執行指令。scrapy

更多方法:

  • 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("\033[41;1m紅燈\033[0m")
12             time.sleep(1)
13         elif count>10:
14             count=0
15             event.set()
16         else:
17             print("\033[42;1m綠燈\033[0m")
18             time.sleep(1)
19         count+=1
20 
21 def car(name):
22     while True:
23         if event.is_set():  #判斷是否設定
24             print("\033[32;1m[%s] run...\033[0m"%name)
25             time.sleep(1)
26         else:
27             print('\033[31;1m [%s] stop...'%name)
28             event.wait()    #等待設定
29             print('\033[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('\n')
10 
11 def f(name):
12     info('\033[31;1mcalled from child process function f\033[0m')
13     print('hello', name)
14 
15 if __name__ == '__main__':
16     info('\033[32;1mmain process line\033[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("\033[32;1m[producer]\033[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

總結:

一、多進程,多線程,協程

操做系統方面的多進程,多線程,協程:

操做系統能夠開多個進程,一個進程能夠有多個線程,多個線程能夠被分配到不一樣的核心上跑,但實際上每一個核心上只有一個線程,只是這個線程在不停的進行上下文的切換,給咱們一種併發的感受。

協程:單線程的調度機制。它的做用是讓原來要使用異步+回調方式(調用線程)寫的非人類代碼,能夠用看似同步的方式寫出來。協程是先出現的,但它有明顯的時間差,沒有併發的感受,因此出現了線程。

python的多進程,多線程,協程:

但python的多線程只能在一個核心上跑(創始人沒想到會有多核出現),就是單核的上下文切換,因此很雞肋。因而協程在python大展拳腳,好多框架都是使用協程來解決多任務的,而不是線程(scrapy,tornado)。

python中多進程,多線程,協程的使用:

IO密集型:多線程/協程(能夠用異步),cpu佔用率低,單個cpu核心就夠了

CPU密集型:多進程,多給它幾個核心提高性能

二、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, 由於每一個子線程的超時開始時刻是上一個子線程超時結束的時刻。

相關文章
相關標籤/搜索