Python之進程、線程、鎖


一、什么是線程。什么是進程。

一個應用程序:軟件
一個應用程序:可以創建多個進程(默認一個進程),一個進程可以創建多個線程(默認一個線程)

線程:工作的最小單元,共享進程中的所有資源,每個線程分擔一點任務,最終完成最后的結果
進程:獨立開辟內存 進程之間的數據隔離,最小資源單位
總結: 1.操作系統幫助開發者操作硬件 
       2.程序員寫好代碼在操作系統上運行

任務特別多:
    3.串行 一個個的去執行
      1 寫好代碼
      2 交給解釋器運行
      3 解釋器讀取代碼,再交給操作系統執行,根據寫的代碼選擇創建線程/進程去執行(單進程/單線程)

    4.多線程的話
      1 寫好代碼
      2 交給解釋器運行
      3 解釋器讀取代碼,再交給操作系統執行,根據寫的代碼選擇創建線程/進程去執行(單進程/多線程)

 

       GIL:Python內置的全局解釋器鎖:用於限制一個進程中同一時刻只有一個線程被cpu調度
         線程之間切換默認gil鎖在執行100個cpu指令的時候就切換 還有一個就是過期時間

          為什么還保留這把鎖?對於語言的創始人來說:在開發這門語言時候,目的是最開始為了方便快速的把這個語言開發出來,假如沒有這把鎖處理的事情非常多,比如一個進程里邊有三個線程同時被CPU調度,那么這三個線程同時修改一個值,這里就要處理,還有就是線程執行到一半要終端,那么就要記住本次線程操作的狀態下次回來執行這個線程的時候繼續從這里開始執行。

          線程創建的越多越好嗎?不是的  線程之間進行切換,要進行上下文管理CPU分片處理要記錄線程運行狀態

      

1 import sys
2 
3 v = sys.getcheckinterval()
4 print(v)

 

    IO操作不占用CPU
Python多線程情況下:
    計算密集型操作:效率低(GIL鎖)
    IO密集型操作:效率高

Python多進程情況下:
    計算密集型操作:效率高(浪費資源)不得已而為之
    IO密集型操作:效率高 (浪費資源)

Java多線程情況下:
    計算密集型操作:效率高
    IO密集型操作:效率高

Java多進程程情況下:(Java程序員一般不寫多進程)
    計算密集型操作:效率高
    IO密集型操作:效率高

自己畫了圖,圖解非常清晰很容易理解(對比Java)

實例一:主線程默認等子線程執行完畢

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import threading
 5 import time
 6 
 7 def func(arg):
 8     time.sleep(arg)
 9     print(arg)
10 
11 
12 t1 = threading.Thread(target=func, args=(3,))
13 t1.start()
14 
15 t2 = threading.Thread(target=func, args=(9,))
16 t2.start()
17 
18 print('123')

 

執行結果:

123
3
9

 

實例二、主線程不再等子線程,主線程終止則所有子線程終止

 1 # def func(arg):
 2 #     time.sleep(arg)
 3 #     print(arg)
 4 #
 5 #
 6 # t1 = threading.Thread(target=func, args=(3,))
 7 # t1.setDaemon(True)  # 不等子線程
 8 # t1.start()
 9 #
10 # t2 = threading.Thread(target=func, args=(9,))
11 # t2.setDaemon(True)
12 # t2.start()
13 #
14 # print(123)
15 
16 # 123

 

實例三、開發者可以控制主線程等待子線程(最多等待時間)

 1 # def func(arg):
 2 #     time.sleep(10)
 3 #     print(arg)
 4 #
 5 #
 6 # print('創建子線程t1')
 7 # t1 = threading.Thread(target=func, args=(3,))
 8 # t1.start()
 9 # t1.join()  # 讓主線程在這里等着,等到子線程t1執行完畢繼續往下走
10 # # t1.join(1)  # 主線程最多等1s
11 # print('創建子線程t2')
12 # t2 = threading.Thread(target=func, args=(9,))
13 # t2.start()
14 # t2.join()  # 讓主線程在這里等着,等到子線程t2執行完畢繼續往下走  這樣搞純粹沒意義了開線程沒卵用就是串行了
15 # # t2.join(1) # 主線程最多等1s
16 # print(123)
17 # 創建子線程t1
18 # 3
19 # 創建子線程t2
20 # 9
21 # 123

 

實例四、獲取線程名稱

 1 # def func(arg):
 2 #     # 獲取當前執行該函數的線程的名稱
 3 #     t = threading.current_thread()
 4 #     name = t.getName()
 5 #     print(name, arg)
 6 #
 7 #
 8 # t1 = threading.Thread(target=func, args=(3,))
 9 # t1.setName('t111111')
10 # t1.start()
11 #
12 #
13 # t2 = threading.Thread(target=func, args=(9,))
14 # t2.setName('t222222')
15 # t2.start()
16 #
17 # print(123)

 

實例五、線程本質

 1 # 先打印 3?還是123?  這個不確定  要看cpu現在忙不忙,有沒有空余時間
 2 # def func(arg):
 3 #     print(arg)
 4 #
 5 #
 6 # t1 = threading.Thread(target=func, args=(3,))
 7 # t1.start()  # 是開始運行線程嗎?  不是
 8 # # start告訴cpu 我已經准備好了,你可以調度我了
 9 #
10 #
11 # print(123)

 

實例六、面向對象版本的線程

自己寫run方法讓自己的線程去執行自己想執行的任務,就不走Thread里邊內置的run方法了

 1 class MyThread(threading.Thread):
 2     # 看源碼發現的
 3     """
 4     self._target = target
 5     if self._target:
 6         self._target(*self._args, **self._kwargs)
 7     """
 8     def run(self):
 9         print('11111', self._args, self._kwargs)
10 
11 
12 # def func(arg):
13 #     print(arg)
14 
15 
16 # t1 = MyThread(target=func, args=(11,))
17 t1 = MyThread(args=(11,), kwargs={'name': 1})
18 t1.start()
19 # 11111 (11,) {'name': 1}

 實例七:多線程的問題 

    如果現在要對全局的一個變量值進行修改,那么線程拿到的數據有可能不安全的

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 lock = threading.Lock()
 8 n = 5
 9 
10 
11 def func(i):
12     print('這段代碼不加鎖', i)
13 
14     lock.acquire()  # 加鎖  此區域的代碼同一時刻只能一個線程執行
15     global n
16     print('當前線程', i, '讀取到n的值', n)
17     n = i
18     time.sleep(1)
19     print('當前線程', i, '修改n的值', n)
20     lock.release()  # 釋放鎖
21 
22 
23 for i in range(5):
24     t = threading.Thread(target=func, args=(i,))
25     t.start()

 

執行結果:

 1 這段代碼不加鎖 0
 2 當前線程 0 讀取到n的值 5
 3 這段代碼不加鎖 1
 4 這段代碼不加鎖 2
 5 這段代碼不加鎖 3
 6 這段代碼不加鎖 4
 7 當前線程 0 修改n的值 0
 8 當前線程 1 讀取到n的值 0
 9 當前線程 1 修改n的值 1
10 當前線程 2 讀取到n的值 1
11 當前線程 2 修改n的值 2
12 當前線程 3 讀取到n的值 2
13 當前線程 3 修改n的值 3
14 當前線程 4 讀取到n的值 3
15 當前線程 4 修改n的值 4
16 
17 這段代碼不加鎖 0
18 當前線程 0 讀取到n的值 5
19 這段代碼不加鎖 1
20 當前線程 1 讀取到n的值 0
21 這段代碼不加鎖 2
22 當前線程 2 讀取到n的值 1
23 這段代碼不加鎖 3
24 當前線程 3 讀取到n的值 2
25 這段代碼不加鎖 4
26 當前線程 4 讀取到n的值 3
27 當前線程 2 修改n的值 4
28 當前線程 1 修改n的值 4
29 當前線程 0 修改n的值 4
30 當前線程 4 修改n的值 4
31 當前線程 3 修改n的值 4
奇葩結果

 

二、鎖

為什么要用鎖?

解決線程不安全+程序員可控制一段代碼

線程安全:多線程操作時,內部會讓所有線程排隊處理 如:列表 字典 Queue

線程不安全:不安全就要上鎖讓所有線程進行排隊處理

實例一:線程安全實例  

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import threading
 5 
 6 v = []
 7 
 8 
 9 def task(arg):
10     v.append(arg)  # 線程安全 要放就要等它放完
11     print(v)
12 
13 
14 for i in range(10):
15     t = threading.Thread(target=task, args=(i,))
16     t.start()

 Rlock與Lock的比較

RLock 一次放行一個,支持鎖多次解多次

lock 一次放行一個線程,鎖一次 解一次 不能同時加幾把鎖 不然一個線程都進不來(死鎖) 基本不用

 實例二:線程不安全  需求往列表中添加線程號 然后再拿出最后一個線程號

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 # lock = threading.Lock()  
 7 lock = threading.RLock()  
 8 
 9 
10 def task(arg):
11     lock.acquire()
12     lock.acquire()
13     v.append(arg)
14     time.sleep(0.01)
15     m = v[-1]
16     print(arg, m)
17     lock.release()
18     lock.release()
19 
20 
21 for i in range(10):
22     t = threading.Thread(target=task, args=(i,))
23     t.start()

 

實例三、BoundedSemaphore信號量 定死了放行的幾個

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 # 鎖:BoundedSemaphore信號量 定死了放行的幾個
 8 lock = threading.BoundedSemaphore(3)
 9 """
10     def __init__(self, value=1):# 默認一個 
11         Semaphore.__init__(self, value)
12         self._initial_value = value
13 """
14 
15 
16 def task(arg):
17     lock.acquire()  # 一次放行三個
18     print(arg)
19     time.sleep(1)
20     lock.release()
21 
22 
23 for i in range(15):
24     t = threading.Thread(target=task, args=(i,))
25     t.start()

 

 實例四、根據輸入放行線程個數來放行線程Condition

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import time
import threading

lock = threading.Condition()  
def task(arg):
    print('線程進來了')
    lock.acquire()   
    print(111111)
    lock.wait()  # 加鎖
    print(arg)
    time.sleep(1)
    lock.release()


for i in range(15):
    t = threading.Thread(target=task, args=(i,))
    t.start()

while True:
    inp = int(input(">>>"))
    lock.acquire()
    lock.notify(inp)
    lock.release()

 

另外一種方法:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 lock = threading.Condition()  # 根據輸入放線程通過
 8 
 9 
10 def xxx():
11     print('來執行啦')
12     input('>>>')
13     return True
14 
15 
16 def task(arg):
17     print('線程進來了')
18     lock.wait_for(xxx)  # 滿足條件就往下走
19     print(arg)
20     time.sleep(1)
21 
22 
23 for i in range(15):
24     t = threading.Thread(target=task, args=(i,))
25     t.start()

 

 實例五、Event一次放所有 理解成紅綠燈就行了

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import threading
 5 
 6 lock = threading.Event()
 7 
 8 
 9 def task(arg):
10     print('線程來了')
11     lock.wait()  # 加鎖  紅燈
12     print(arg)
13 
14 
15 for i in range(5):
16     t = threading.Thread(target=task, args=(i,))
17     t.start()
18 
19 input(">>>")
20 lock.set()  # 綠燈
21 lock.clear()  # 再次變紅燈
22 for i in range(5):
23     t = threading.Thread(target=task, args=(i,))
24     t.start()
25 lock.set()  # 綠燈

 

實例六、給當前線程創建空間用於存儲值threadinglocal

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 v = threading.local()
 8 
 9 
10 def func(arg):
11     # 內部會為當前線程創建一個空間用於存儲值
12     v.phone = arg
13     time.sleep(2)
14     # 然后就可以在當前獨立的空間取值
15     print(v.phone, arg)
16 
17 
18 for i in range(5):
19     t = threading.Thread(target=func, args=(i,))
20     t.start()

 

 三、線程池   

  一次最多放行幾個線程,超過線程池的個數,會下一批在放行

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 from concurrent.futures import ThreadPoolExecutor
 5 import time
 6 
 7 
 8 def task(a1, a2):
 9     time.sleep(2)
10     print(a1, a2)
11 
12 
13 pool = ThreadPoolExecutor(3)  # 一次最多放行3個 如果超過3個就得分次數了 12/3
14 
15 for i in range(12):
16     pool.submit(task, i, 2)

 

四、生產者消費者  使用多線程+隊列來實現模型

解決了消費者不用在那里等生產者生產東西,兩個對象互不影響(比如:買車票,假如有一個管道,我們生產者就把信息放到管道中去,由車票售員去管道拿信息消費任務)

隊列 先進先出
棧 后進先出(上子彈夾)
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 import queue
 7 
 8 q = queue.Queue()
 9 
10 
11 def producer(arg):
12     """
13     生產者
14     :param arg:
15     :return:
16     """
17     while True:
18         time.sleep(1)
19         print("廚師生產包子%s號" % arg)
20         q.put(arg)
21 
22 
23 for i in range(1, 4):
24     t = threading.Thread(target=producer, args=(i,))
25     t.start()
26 
27 
28 def consumer(arg):
29     """
30     消費者
31     :param arg:
32     :return:
33     """
34     while True:
35         time.sleep(1)
36         q.get(arg)
37         print("消費者吃包子")
38 
39 
40 for i in range(1, 3):
41     t = threading.Thread(target=consumer, args=(i,))
42     t.start()

 五、進程

 注意:在windows下和linux下碼代碼不一樣   在windows下執行要放在main下執行

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 
 7 def task(arg):
 8     print(arg)
 9 
10 
11 if __name__ == '__main__':
12     for i in range(10):
13         p = multiprocessing.Process(target=task, args=(i,))
14         p.start()

 

實例一、進程之間數據不共享

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 data_list = []
 7 
 8 
 9 def task(arg):
10     data_list.append(arg)
11     print(data_list)
12 
13 
14 if __name__ == '__main__':
15     for i in range(10):
16         p = multiprocessing.Process(target=task, args=(i,))
17         p.start()
18 """
19 [0][1][2][3][4][5][6][7][8][9]
20 """

 

實例 進程常規操作

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import time
import multiprocessing


def task(arg):
    p = multiprocessing.current_process()
    print(p.name, p.ident, p.pid)
    time.sleep(2)
    print(arg)


def run():
    print('111')
    p1 = multiprocessing.Process(target=task, args=(1,))
    # p1.daemon = True  # 主進程執行完畢直接不等子進程了默認False
    p1.start()
    p1.name = 'pp1'
    # p1.join(9)  # 等待進程完畢  最多9秒
    print('222')

    p2 = multiprocessing.Process(target=task, args=(2,))
    # p2.daemon = True
    p2.start()
    p2.name = 'pp2'
    # p2.join()
    print('333')


if __name__ == '__main__':
    run()

 

實例二、進程間的數據共享方式一multiprocessing.Queue()

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 
 7 def task(arg, q):
 8     q.put(arg)
 9 
10 
11 if __name__ == '__main__':
12     q = multiprocessing.Queue()  # 進程間數據共享
13     for i in range(10):
14         p = multiprocessing.Process(target=task, args=(i, q))
15         p.start()
16 
17     while True:
18         v = q.get()
19         print(v)

 

 實例三、進程中數據共享的方式二multiprocessing.Manager()

 

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 
 7 def task(arg, dic):
 8     dic[arg] = 100
 9 
10 
11 if __name__ == '__main__':
12     m = multiprocessing.Manager()
13     dic = m.dict()
14     for i in range(10):
15         p = multiprocessing.Process(target=task, args=(i, dic,))
16         p.start()
17         p.join()
18 
19     # v = dic.items()
20     # print(v)
21     print(dic)

 

實例四、進程鎖 和線程差不多

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 
4 import multiprocessing
5 
6 lock = multiprocessing.RLock()
7 # 進程鎖跟線程鎖差不多  就是進程之間用同一數據的時候就加鎖

 

實例五、進程池

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from concurrent.futures import ProcessPoolExecutor

pool = ProcessPoolExecutor(5)

 


免責聲明!

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



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