02python 中的線程

線程

線程是什麼?

進程是資源分配的最小單位,線程是CPU調度的最小單位.每一個進程中至少有一個線程。

爲何有了進程還須要線程?

60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨着計算機技術的發展,進程出現了不少弊端,一是因爲進程是資源擁有者,建立、撤消與切換存在較大的時空開銷,所以須要引入輕型進程;二是因爲對稱多處理機(SMP)出現,能夠知足多個運行單位,而多個進程並行開銷過大。(官方說法)
進程的缺點:
  • 進程在執行任務中若是堵塞,則整個進程就會被掛起
  • 進程在同一時間只能執行一個任務(開子進程也只是爲了解決一個任務)
  • 進程之間的數據是隔離的。想要取到不一樣進程之間的數據很難
  • 多個進程並行開銷過大(時間長)
瞭解了進程的缺點了以後,線程的概念就出來了。線程正好就是解決這些問題的。

經過代碼來解釋線程的功能以及特性

一、線程的建立
from threading import Thread


def func(n):
    print("線程的建立", n)


t = Thread(target=func, args=('pontoon',))
t.start()

 

# 多線程併發
import os
import time
from threading import Thread
from multiprocessing import Process


def func():
    time.sleep(2)
    print('hello', os.getpid())


if __name__ == '__main__':
    # for i in range(10):
    #     p1 = Process(target=func)
    #     p1.start()
    #     print('主進程pid', os.getpid())
    #     print('-' * 30)

    for i in range(10):
        t1 = Thread(target=func)
        t1.start()
        print('主線程pid', os.getpid())
        
# 結果我就不貼出來了        
# 二者並無什麼實質上的區別,固然也看的出一點點差別就是主線程執行(所有一塊兒出結果)要比主進程快(5個5個來)
二、線程與進程的對比
一、pid的對比
 1 import os
 2 from threading import Thread
 3 from multiprocessing import Process
 4 
 5 
 6 def func():
 7     print('hello', os.getpid())
 8 
 9 
10 if __name__ == '__main__':
11     p1 = Process(target=func)
12     p2 = Process(target=func)
13     p1.start()
14     p2.start()
15     print('主進程pid', os.getpid())
16     print('-' * 30)
17 
18     t1 = Thread(target=func)
19     t2 = Thread(target=func)
20     t1.start()
21     t2.start()
22     print('主線程pid', os.getpid())
23     
24 # 注意執行順序  程序從上往下執行,最後執行子進程   
25 >>>主進程pid 155928
26 ------------------------------
27 hello 155928        # 子線程
28 hello 155928        # 子線程
29 主線程pid 155928
30 hello 134120        # 子進程
31 hello 74396         # 子進程
View Code
  • 從上述代碼的執行結果來看全部線程的pid都指向一個155928即主進程的pid,能夠的出結論:一個進程能夠包含至少一個線程。
  • 從代碼的執行順序來看。主進程會隨着子進程代碼的執行完畢才結束,可是主線程與子線程則是從上向下按順序執行。

 

二、開啓效率的對比:
 1 # 開啓效率的對比:
 2 import os
 3 import time
 4 from threading import Thread
 5 from multiprocessing import Process
 6 
 7 
 8 def func():
 9     time.sleep(2)
10     print('hello', os.getpid())
11 
12 
13 if __name__ == '__main__':
14     # start_time = time.time()
15     # for i in range(30):
16     #     p1 = Process(target=func)
17     #     p1.start()
18     #     print('主進程pid', os.getpid())
19     # end_time = time.time()
20     # print('併發進程所用時間:',end_time - start_time)
21 
22     start_time = time.time()
23     for i in range(30):
24         t1 = Thread(target=func)
25         t1.start()
26         print('主線程pid', os.getpid())
27     end_time = time.time()
28     print('併發線程所用時間:', end_time - start_time)
29 
30 >>>併發線程所用時間: 0.006010770797729492
31     併發進程所用時間: 1.3949854373931885
32 # 差距至關大
View Code
三、內存數據的共享
 1 # 三、線程之間數據的共享問題
 2 
 3 import os
 4 from threading import Thread
 5 
 6 
 7 def func():
 8     global g
 9     g = 44
10     print(g, os.getpid())
11 
12 
13 g = 100
14 t_list = []
15 for i in range(3):
16     t = Thread(target=func)
17     t.start()
18     t_list.append(t)
19 
20 for i in t_list: t.join()
21 print(g)
22 
23 >>>44 112740
24 44 112740
25 44 112740
26 44
View Code
四、多線程中的input
 1 import os
 2 from threading import Thread
 3 
 4 
 5 def func(n):
 6     inp = input('%s:' % n)
 7     print(inp)
 8 
 9 
10 for i in range(3):
11     t = Thread(target=func, args=('name%s' % i, ))
12     t.start()
13 
14 >>>name0:name1:name2:fgh
15 fgh
16 # 有點懵逼
View Code
總結
進程是最小的內存分配單位
線程是操做系統調度的最小單位

進程中至少含有一個線程
進程中能夠開啓多個線程

開啓線程的時間要遠小於進程
多個線程內部都有本身的數據棧,數據棧中的數據不共享
全局變量在多線程之間是共享的
多線程是可使用Input的(IO請求並不會堵塞)

守護線程 / 守護進程

必問的一個面試題   GIL(全局解釋器鎖

在cpython解釋器中爲了防止線程競爭,致使數據不安全會在進程中加上GIL鎖,用來保證在同一個進程在同一時刻,只有一個線程被CPU調度。

線程鎖

一段代碼引出線程鎖的概念
from threading import Thread, Lock
import time


def func():
    global n
    temp = n
    time.sleep(1)
    n = temp - 1


n = 10        # 線程的數據是共享的
t_list = []
for i in range(10):
    t = Thread(target=func)
    t.start()
    t_list.append(t)

for i in t_list:
    i.join()

print(n)

>>>9        # 獲得的爲何會是一個9
畫圖進行解釋:
這個時候你還會問,不是有GIL嗎?線程被鎖住了不該該一個一個的去取數據嗎?
用到了GIL以後數據仍然不安全。
操做系統採用時間片輪轉算法來執行線程(假設是0.01s),一次線程的執行需1s才能執行完成。可想而知,若是隻是單純的加上GIL數據仍然是不安全的。
線程鎖能確保一個線程執行完一次任務,因此在加上鎖就能解決這個問題!
 1 def func(lock):
 2     lock.acquire()
 3     global n
 4     temp = n
 5     time.sleep(1)
 6     n = temp - 1
 7     lock.release()
 8 
 9 
10 n = 10
11 lock = Lock()
12 t_list = []
13 for i in range(10):
14     t = Thread(target=func, args=(lock, ))
15     t.start()
16     t_list.append(t)
17 
18 for i in t_list:
19     i.join()
20 
21 >>>0    
View Code
這裏還會出現一個問題就是科學家吃麪的問題——死鎖(互斥鎖)
 1 import time
 2 from threading import Thread, Lock
 3 
 4 noodle_lock = Lock()
 5 fork_lock = Lock()
 6 
 7 
 8 def eat1(name):
 9     noodle_lock.acquire()
10     print('%s 搶到了麪條'%name)
11     fork_lock.acquire()
12     print('%s 搶到了叉子'%name)
13     print('%s 吃麪'%name)
14     fork_lock.release()
15     noodle_lock.release()
16 
17 
18 def eat2(name):
19     fork_lock.acquire()
20     print('%s 搶到了叉子' % name)
21     time.sleep(1)
22     noodle_lock.acquire()
23     print('%s 搶到了麪條' % name)
24     print('%s 吃麪' % name)
25     noodle_lock.release()
26     fork_lock.release()
27 
28 
29 for name in ['aaa', 'bbb', 'ccc']:
30     t1 = Thread(target=eat1, args=(name,))
31     t2 = Thread(target=eat2, args=(name,))
32     t1.start()
33     t2.start()
34     
35 >>>aaa 搶到了麪條
36 aaa 搶到了叉子
37 aaa 吃麪
38 aaa 搶到了叉子
39 bbb 搶到了麪條    
View Code
什麼叫互斥鎖
是指兩個或兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。上述的代碼就是死鎖現象。
大白話來說就是多個線程、進程碰見多個鎖,就會產生死鎖現象。
解決死鎖,遞歸鎖
什麼叫作遞歸鎖?
下圖基本上就解釋了遞歸鎖的概念
在Python中爲了支持在同一線程中屢次請求同一資源,python提供了可重入鎖RLock。
這個RLock內部維護着一個Lock和一個counter變量,counter記錄了acquire的次數,從而使得資源能夠被屢次require。直到一個線程全部的acquire都被release,其餘的線程才能得到資源。上面的例子若是使用RLock代替Lock。
用遞歸鎖解決死鎖代碼
 1 import time
 2 from threading import Thread, Rock
 3 
 4 
 5 fork_lock = noodle_lock = RLock()        # 注意這種寫法
 6 def eat1(name):
 7     noodle_lock.acquire()
 8     print('%s 搶到了麪條'%name)
 9     fork_lock.acquire()
10     print('%s 搶到了叉子'%name)
11     print('%s 吃麪'%name)
12     fork_lock.release()
13     noodle_lock.release()
14 
15 
16 def eat2(name):
17     fork_lock.acquire()
18     print('%s 搶到了叉子' % name)
19     time.sleep(1)
20     noodle_lock.acquire()
21     print('%s 搶到了麪條' % name)
22     print('%s 吃麪' % name)
23     noodle_lock.release()
24     fork_lock.release()
25 
26 
27 for name in ['aaa', 'bbb', 'ccc']:
28     t1 = Thread(target=eat1, args=(name,))
29     t2 = Thread(target=eat2, args=(name,))
30     t1.start()
31     t2.start()
32     
33 aaa 搶到了麪條
34 aaa 搶到了叉子
35 aaa 吃麪
36 aaa 搶到了叉子
37 aaa 搶到了麪條
38 aaa 吃麪
39 bbb 搶到了麪條
40 bbb 搶到了叉子
41 bbb 吃麪
42 bbb 搶到了叉子
43 bbb 搶到了麪條
44 bbb 吃麪
45 ccc 搶到了麪條
46 ccc 搶到了叉子
47 ccc 吃麪
48 ccc 搶到了叉子
49 ccc 搶到了麪條
50 ccc 吃麪    
View Code

信號量

 1 # 七、信號量
 2 
 3 from threading import Thread,Semaphore
 4 import threading
 5 import time
 6 
 7 
 8 def func():
 9     sm.acquire()
10     print('%s get sm' %threading.current_thread().getName())
11     time.sleep(3)
12     sm.release()
13     
14     
15 if __name__ == '__main__':
16     sm=Semaphore(5)
17     for i in range(23):
18         t=Thread(target=func)
19         t.start()
View Code
同進程:規定鎖的個數

事件

同進程
 1 import threading
 2 import time,random
 3 from threading import Thread,Event
 4 
 5 def conn_mysql():
 6     count=1
 7     while not event.is_set():
 8         if count > 3:
 9             raise TimeoutError('連接超時')
10         print('<%s>第%s次嘗試連接' % (threading.current_thread().getName(), count))
11         event.wait(0.5)
12         count+=1
13     print('<%s>連接成功' %threading.current_thread().getName())
14 
15 
16 def check_mysql():
17     print('\033[45m[%s]正在檢查mysql\033[0m' % threading.current_thread().getName())
18     time.sleep(random.randint(2,4))
19     event.set()
20 if __name__ == '__main__':
21     event=Event()
22     conn1=Thread(target=conn_mysql)
23     conn2=Thread(target=conn_mysql)
24     check=Thread(target=check_mysql)
25 
26     conn1.start()
27     conn2.start()
28     check.start()
View Code

條件

 1 from threading import Condition, Thread
 2 
 3 
 4 def func(con, i):
 5     con.acquire()
 6     con.wait()
 7     print('在第{0}個循環裏'.format(i))
 8     con.release()
 9 
10 
11 con = Condition()
12 for i in range(10):
13     Thread(target=func, args=(con, i)).start()
14 while True:
15     num = int(input('>>>'))
16     con.acquire()
17     con.notify(num)     # 在鑰匙
18     con.release()
View Code

線程隊列

爲何線程可以進行數據共享還要在用隊列?
由於數據安全的問題。隊列中內置了鎖。保證數據安全!
就那幾個方法,沒啥好說的詳見女神博客

線程池 (concurrent.futures)

 1 import time
 2 from concurrent.futures import ThreadPoolExecutor
 3 
 4 
 5 def func(n):
 6     time.sleep(2)
 7     print(n)
 8     return n * n
 9 
10 
11 t_pool = ThreadPoolExecutor(max_workers=5)      # 在線程池中建立5個線程
12 t_list = []
13 for i in range(20):
14     t = t_pool.submit(func, i)      # 建立子線程
15     t_list.append(t)
16 
17 t_pool.shutdown()           # close() + join() 方法
18 print('主線程')
19 for i in t_list:
20     print("***", t.result())    # 取return的結果
View Code
線程池中增長回調函數
 1 # 十一、線程池中加回調函數
 2 import time
 3 from concurrent.futures import ThreadPoolExecutor
 4 
 5 
 6 def func(n):
 7     time.sleep(2)
 8     print(n)
 9     return n * n
10 
11 
12 def call_back(m):
13     print("結果是 %s" % m.result())
14 
15 
16 t_pool = ThreadPoolExecutor(max_workers=5)      #
17 for i in range(20):
18     t_pool.submit(func, i).add_done_callback(call_back)
19     
20 >>>0
21 結果是 0
22 4
23 3
24 結果是 9
25 結果是 16
26  ...
View Code
相關文章
相關標籤/搜索