python學習筆記(九)——線程與進程

一、線程

Python 中爲我們提供了兩個模塊來創建線程。

  • _thread
  • threading

thread 模塊已被廢棄。用戶可以使用 threading 模塊代替。所以,在 Python 中不能再使用"thread" 模塊。爲了兼容性,Python 將 thread 重命名爲 “_thread”。

相對 _thread 模塊來說, threading 模塊更加高級也更加常用。

多線程

創建多線程步驟:

  1. 導入線程庫
  2. 創建任務函數
  3. 創建線程對象
  4. 啓動線程

實例:

import threading,time

def task():
    for i in range(1,11):
        time.sleep(1)
        print(threading.current_thread().name,end="")
        print(":output >%d"%i)

print(time.ctime())
# 創建兩個線程
pt1=threading.Thread( target=task )
pt2=threading.Thread( target=task )
# 輸出線程名
print(threading.current_thread().name)
# 啓動線程
pt1.start()
pt2.start()
pt1.join()
pt2.join()
print(time.ctime())

輸出:

Sat Mar 14 16:07:12 2020
MainThread
Thread-2:output >1
Thread-1:output >1
Thread-1Thread-2:output >2
:output >2
Thread-1Thread-2:output >3
:output >3
Thread-1Thread-2:output >4
:output >4
Thread-1:output >5
Thread-2:output >5
Thread-1Thread-2:output >6
:output >6
Thread-2:output >7
Thread-1:output >7
Thread-2Thread-1:output >8
:output >8
Thread-2:output >9
Thread-1:output >9
Thread-1:output >10
Thread-2:output >10
Sat Mar 14 16:07:22 2020

可以看到一共用了10秒時間,兩個線程都完成了自己的任務。

使用 threading.Thread( ) 創建線程時,可以傳入六個參數,比如線程名 name,任務函數參數 args等。


    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None):

其中,如name、daemon等參數除了在創建線程時指定以外,還可以通過對象調用函數設置。 pt.setName()、pt.setDaemon()。

線程同步

在多線程中會常會發生多個線程同時訪問同一個資源的情況,造成線程不安全。而我們要求線程對臨界資源的操作是必須是原子操作,因此我們可以通過線程鎖來實現線程安全。

使用 Thread 對象的 Lock 和 Rlock 可以實現簡單的線程同步,這兩個對象都有 acquire 方法和 release 方法進行加鎖和解鎖。

lock=threading.RLock()		# 創建一個鎖🔒
lock.acquire()		# 加鎖
lock.release()		# 解鎖

把每次只允許一個線程操作的數據,放到 acquire 和 release 方法之間進行操作。

全局解釋器鎖GIL

在非python環境中,單核情況下,同時只能有一一個任務執行。多核時可以支持多個線程同時執行。但是在python中,無論有多少核,同時只能執行一個線程。究其原因,這就是由於GIL的存在導致的。

GIL的全稱是Global Interpreter Lock(全局解釋器鎖),來源是python設計之初的考慮,爲了數據安全所做的決定。某個線程想要執行,必須先拿到GIL,我們可以把GIL看作是“通行證”,並且在一個python進程中,GIL只有一 一個。拿不到通行證的線程,就不允許進入CPU執行。GIL 只在cpython中才有,因爲cpython調用的是c語言的原生線程,所以他不能直接操作cpu,只能利用GIL保證同一-時間只能有- -個線程拿到數據。而在pypy和ipython中是沒有GIL的。

所以,在Python中,可以使用多線程,但不要指望能有效利用多核。不過,也不用過於擔心,Python雖然不能利用多線程實現多核任務,但可以通過多進程實現多核任務。多個Python進程有各自獨立的GIL鎖,互不影響

二、進程

使用多進程同樣可以完成多線程的工作,我們將之前的多線程程序改成多進程程序。

import multiprocessing
import time

def task():
    for i in range(1,11):
        time.sleep(1)
        print(multiprocessing.current_process().name,__name__,end="")
        print(":output >%d"%i)

if __name__=="__main__":
    print(time.ctime())
    p1=multiprocessing.Process(target=task)
    p2=multiprocessing.Process(target=task)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print(time.ctime())

輸出:

Sat Mar 14 17:25:31 2020
Process-1 __mp_main__:output >1
Process-2 __mp_main__:output >1
Process-1 __mp_main__:output >2
Process-2 __mp_main__:output >2
Process-1 __mp_main__:output >3
Process-2 __mp_main__:output >3
Process-1 __mp_main__:output >4
Process-2 __mp_main__:output >4
Process-1 __mp_main__:output >5
Process-2 __mp_main__:output >5
Process-1 __mp_main__:output >6
Process-2 __mp_main__:output >6
Process-1 __mp_main__:output >7
Process-2 __mp_main__:output >7
Process-1 __mp_main__:output >8
Process-2 __mp_main__:output >8
Process-1 __mp_main__:output >9
Process-2 __mp_main__:output >9
Process-1 __mp_main__:output >10
Process-2 __mp_main__:output >10
Sat Mar 14 17:25:41 2020
進程池

通過進程池 pool 可以批量的創建大量的進程。

import multiprocessing
import os
import time

def task(i):
    print("線程%d 執行:  pid=%s"%(i,os.getpid()))
    time.sleep(1)
    return i

def back(n):
    print("任務%d已完成"%n)

if __name__=="__main__":
    mypool=multiprocessing.Pool(5)  # 線程池大小
    for i in range(1,21):           # 20個線程任務
        mypool.apply_async(func=task,args=(i,))
    mypool.close()
    mypool.join()

apply_async() 會等待線程池中有空閒時依次執行線程任務。其中,callback 參數是回調函數。

進程池中回調函數callback作用是:進程池中任何一個任務一旦處理完了,就立即告知主進程,主進程則調用一個函數去處理該結果,它可以用來接收進程任務的返回值,判斷其執行的結果。

輸出結果:

線程1 執行:  pid=12372
線程2 執行:  pid=11236
線程3 執行:  pid=15144
線程4 執行:  pid=8332
線程5 執行:  pid=16732
線程6 執行:  pid=12372
任務1已完成
線程7 執行:  pid=11236
任務2已完成
線程8 執行:  pid=15144
任務3已完成
線程9 執行:  pid=8332
任務4已完成
線程10 執行:  pid=16732
任務5已完成
線程11 執行:  pid=12372
任務6已完成
線程12 執行:  pid=11236
任務7已完成
線程13 執行:  pid=15144
任務8已完成
線程14 執行:  pid=8332
任務9已完成
線程15 執行:  pid=16732
任務10已完成
線程16 執行:  pid=12372
任務11已完成
線程17 執行:  pid=11236
任務12已完成
線程18 執行:  pid=15144
任務13已完成
線程19 執行:  pid=8332
任務14已完成
線程20 執行:  pid=16732
任務15已完成
任務16已完成
任務17已完成
任務18已完成
任務19已完成
任務20已完成

需要注意的是,在使用線程池時應注意將pool.close() 先於 pool.join() 調用。因爲 join() 會讓主進程阻塞等待子進程全部執行完之後再執行,close() 的作用是關閉pool,使其不在接受新的(主進程)任務。所以,兩個順序可不能顛倒了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章