Python學習day40-併發編程(終)

 

Python學習day40-併發編程(終)

線程queue和定時器

線程隊列裏面有幾種有意思的算法模型,好比隊列,堆棧,以及有優先級的取數據,是比較好玩的幾個東西.實例見下面:html

 
 
 
xxxxxxxxxx
22
 
 
 
 
1
import queue
2
# 隊列:先進先出
3
q = queue.Queue()
4
q.put('123')
5
q.put('456')
6
print(q.get())
7
print(q.get())
8
'''
9
123
10
456
11
Process finished with exit code 0
12
能夠看到,程序到此已經結束,其取數據的順序就是先進先出
13
14
比較有趣的是,隊列裏面還有task_done和join兩個方法,其用法和以前所瞭解的JoinableQueue裏面的task_done和join是徹底同樣的,即有幾個put就要有幾個task_done,否則join就會卡主,不會釋放程序
15
'''
16
q.task_done()
17
# q.task_done()
18
q.join()# 這裏就會卡主,程序不會繼續向下執行,也不會自動結束
19
'''
20
123
21
456
22
'''
 
 
 
 
 
xxxxxxxxxx
15
 
 
 
 
1
import queue
2
# 堆棧,先進後出
3
q = queue.LifoQueue()
4
q.put('123')
5
q.put('456')
6
q.put('789')
7
print(q.get())
8
print(q.get())
9
print(q.get())
10
'''
11
結果:
12
789
13
456
14
123
15
'''
 
 
 
 
 
xxxxxxxxxx
18
 
 
 
 
1
import queue
2
# 能夠根據優先級取數據
3
4
q = queue.PriorityQueue()
5
# 這裏要注意,put裏面的第一個參數一般爲int類型,並且值越小優先級越高,包括負數,意思就是全部的負數優先級都高於整數,
6
q.put((-1, 'a'))
7
q.put((80, 'b'))
8
q.put((1, 'c'))
9
10
print(q.get())
11
print(q.get())
12
'''
13
結果爲:
14
(-1, '1')
15
(1, '3')
16
(80, '2')
17
Process finished with exit code 0
18
'''
 
 

定時器,顧名思義,就是定時的意思,咱們設定一個延時,就可讓進程延時這些時間而後再開啓.實例以下:node

 
 
 
xxxxxxxxxx
60
 
 
 
 
1
from threading import Thread, current_thread, Timer
2
import time
3
4
5
def task():
6
    end = time.time()
7
    print(f'線程執行了')
8
    time.sleep(2)
9
    print(f'線程結束了')
10
    print(f'進程開啓延時了{end - start:6.2f}秒')
11
12
13
if __name__ == '__main__':
14
    start = time.time()
15
    t = Timer(4, task)  # Timer()括號裏第一個參數爲延時的時間,第二個爲開啓的線程要運行的函數,這個例子是過4秒後開啓一個線程
16
    t.start()
17
'''
18
執行結果爲:
19
線程執行了
20
線程結束了
21
進程開啓延時了 4.00秒
22
Process finished with exit code 0
23
24
25
到這裏咱們可能會有個疑問,那麼這種延時和直接用time.sleep()有什麼區別呢?
26
區別就在於time.sleep()是整個程序都暫停了,而Timer只是延時開啓進程,對於別的代碼的運行沒有任何影響,咱們能夠在main程序裏面加入兩個print來證實這一點
27
28
'''
29
from threading import Thread, current_thread, Timer
30
import time
31
32
33
def task():
34
    end = time.time()
35
    print(f'線程執行了')
36
    time.sleep(2)
37
    print(f'線程結束了')
38
    print(f'進程開啓延時了{end - start:6.2f}秒')
39
40
41
if __name__ == '__main__':
42
    print('進入main')
43
    start = time.time()
44
    print('準備進入延時')
45
    t = Timer(4, task)  
46
    t.start()
47
    print('main結束')
48
'''
49
執行結果爲:
50
進入main
51
準備進入延時
52
main結束
53
線程執行了
54
線程結束了
55
進程開啓延時了 4.00秒
56
57
Process finished with exit code 0
58
59
上面的執行結果咱們就能夠看出,延時並無影響其他代碼的運行,這點很是難得
60
'''
 
 

 

線程池和進程池

首先咱們要明白進程池和線程池的概念是什麼,所謂池,池子,現實生活中是盛水的,能夠說是一個容器,在咱們的Python裏面也是一個容器,用來容納進程或者線程.python

那麼進程池/線程池的做用就是,當計算機須要併發的任務量遠遠大於計算機所能承受的範圍的時候,或者說計算機沒法一次性開啓過多的任務數的時候,咱們就須要進程池/線程池來限制當前計算機的進程數/線程數,從而保護計算機,或者是服務器,使其不至於宕機,影響業務.web

池所用的模塊是ProcessPoolExecutor和 ThreadPoolExecutor,經常使用的一些方法包括:算法

result(),等待該進程/線程的返回結果,當結果都返回以後,result()纔會打印出來而後執行後面的代碼,這樣會致使全部的任務變成串行,一個線程執行完纔會去執行下一個線程,雖然下降了效率,可是提升了程序的安全性,條理性也會更清楚編程

submit(obj,*args,**kwargs)第一個參數就是所調用的函數名,第二個和第三個都是調用的函數所須要傳入的參數,submit會有一個返回值,因此一般所調用的函數會有返回值,來做爲submit的返回值.windows

shutdown()關閉當前進程池/線程池安全

例程以下:服務器

 
 
 
xxxxxxxxxx
62
 
 
 
 
1
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
2
from threading import currentThread
3
from multiprocessing import current_process
4
import time
5
6
7
def task(i):
8
    print(f'開啓{i}')
9
    print(f'線程{currentThread().name}執行任務{i}')
10
    # print(f'進程{current_process().name}執行任務{i}')
11
    time.sleep(2)
12
    print(f'線程{currentThread().name}結束任務{i}')
13
    print(f"\033[5;31;40m結束{i}\033[0m")
14
15
    return i ** 2
16
17
    # print(f'進程{current_process().name}結束任務{i}')
18
19
20
if __name__ == '__main__':
21
    # pool_p = ProcessPoolExecutor(4)
22
    pool_t = ThreadPoolExecutor(4)
23
    fu_list = []
24
    for i in range(5):
25
        # submit括號裏第一個參數是所調用的函數名task,第二個和第三個是須要傳入的調用函數task的形參,
26
        # submit能夠返回一個對象,因此能夠在task函數裏面寫返回值
27
        future = pool_t.submit(task, i)  # 5個線程,解決20個任務
28
        print(f'執行結果爲{future.result()}')
29
        # 若是沒有結果,會一直等待拿到結果,就會致使全部的任務變成串行,一個線程運行完纔會運行下一個任務
30
        # pool_p.submit(task, i)
31
    pool_t.shutdown()  # 會關閉池的入口,會等到全部的任務執行完,結束阻塞
32
    for fu in fu_list:
33
        print(fu.result())
34
35
'''
36
開啓0
37
線程ThreadPoolExecutor-0_0執行任務0
38
線程ThreadPoolExecutor-0_0結束任務0
39
結束0
40
執行結果爲0
41
開啓1
42
線程ThreadPoolExecutor-0_0執行任務1
43
線程ThreadPoolExecutor-0_0結束任務1
44
結束1
45
執行結果爲1
46
開啓2
47
線程ThreadPoolExecutor-0_1執行任務2
48
線程ThreadPoolExecutor-0_1結束任務2
49
結束2
50
執行結果爲4
51
開啓3
52
線程ThreadPoolExecutor-0_0執行任務3
53
線程ThreadPoolExecutor-0_0結束任務3
54
結束3
55
執行結果爲9
56
開啓4
57
線程ThreadPoolExecutor-0_2執行任務4
58
線程ThreadPoolExecutor-0_2結束任務4
59
結束4
60
執行結果爲16
61
Process finished with exit code 0
62
'''
 
 

同步和異步

同步和異步實際上是提交任務的兩種方式,簡單來講,併發

  1. 同步就是提交了一個任務,必須等任務執行完了才能執行下一行代碼
  2. 異步則是提交了一個任務,不須要等任務執行完,就能夠執行下面的代碼.

協程

簡單來講:協程是一種用戶態的輕量級的線程,是由用戶程序本身控制調度的

協程須要注意的兩點是:

  1. Python的線程是內核級別的,是由操做系統控制調度的
  2. 單線程內開啓的協程,一旦遇到IO就會從應用程序級別而不是操做系統級別進行切換,從而提高效率,這就是協程存在的意義.

爲了正確的使用協程,咱們須要引入gevent模塊(這個不是Python自帶的模塊,須要讀者本身下載,指令pip3 install gevent),並且須要使用到gevent的一個很是有趣的魔法方法,叫作moncky,具體見下例

 
 
 
xxxxxxxxxx
1
28
 
 
 
 
1
from gevent import monkey;monkey.patch_all()  # 咱們須要在開頭加上這一句,由於gevent自己並不能捕獲全部的IO,只能識別自身的延時,因此這個語句至關於一個補丁,能夠識別程序裏面全部的IO,而後遇到這些IO的時候切換至其餘的協程
2
3
import gevent# 導入gevent模塊,這個導入必定要在上面的語句下面,切記!
4
import time
5
6
7
def eat():
8
    print('eat 1')
9
    time.sleep(2)
10
    print('eat 2')
11
12
13
def play():
14
    print('play 1')
15
    time.sleep(3)
16
    print('play 2')
17
18
19
start = time.time()
20
g1 = gevent.spawn(eat)# gevent的用法不一樣於以前學習的進程和線程,是另外一種調用方法,並且下面必定須要用join來阻塞才能得到每一個協程的執行結果,不然程序不會返回任何值,也不會執行開啓協程所寫入的那個任務的內容
21
g2 = gevent.spawn(play)
22
g1.join()
23
g2.join()
24
end = time.time()
25
print(f'{end - start:6.6f}')
26
相關文章
相關標籤/搜索