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


線程,進程

定義:

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

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

進程要想操作CPU,就必須要創建一個線程(進程中至少包含一個線程)

區別:

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

2.同一進程的線程之間可以相互交流 ,2個進程之間的交流必須通過一個中間代理

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

更多方法:

  • 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

總結:

1、多進程,多線程,協程

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

操作系統可以開多個進程,一個進程可以有多個線程,多個線程可以被分配到不同的核心上跑,但實際上每個核心上只有一個線程,只是這個線程在不停的進行上下文的切換,給我們一種並發的感覺。

協程:單線程的調度機制。它的作用是讓原來要使用異步+回調方式(調用線程)寫的非人類代碼,可以用看似同步的方式寫出來。協程是先出現的,但它有明顯的時間差,沒有並發的感覺,所以出現了線程。

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

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

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

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

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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM