Table of Contents
多線程
<a id="orgacb2cbe"></a>
線程概念
所謂線程是操作系統能夠進行運算調度的最小單元,一個線程是一個執行上下文,是cpu執行命令時的一串指令,一個進程能夠并發出多個線程,來執行不同的任務。
<a id="orga1fd5b1"></a>
進程與線程的區別
1.同一進程中的線程共享同一內存空間,但進程之間是獨立的。
2.同一進程中的線程共享數據,但是進程之間的數據是獨立的。
3.同一進程中的線程可以很方便地通信,但是進程之間通信之間的通信則需要通過代理實現。
4.在一個進程中創建一個線程很容易,但是進程創建一個子進程,則fork一個出來,復制一份內存空間
5.主線程可以操縱同一進程中的其他線程,但是進程只能操縱它的子進程。線程啟動快,進程啟動慢。線程之間切換,比較耗時,比較占用cpu,比較適合io密集型任務,
而進程之間的切換比較快速,但是進程fork出很多子進程,比較占用內存空間。對主線程的修改可能會對其他線程造成影響,但是進程和進程,進程和子進程,都是一
個獨立的內存空間,所以一個進程的修改,不會對其他進程造成影響。
<a id="orgfd4eec7"></a>
線程常用方法
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
<colgroup>
<col class="org-left" />
<col class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">方法</th>
<th scope="col" class="org-left">注釋</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">start()</td>
<td class="org-left">線程如果已經準備,啟用該方法,則開始啟動線程</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">setName()</td>
<td class="org-left">為線程設置名稱</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">join()</td>
<td class="org-left">逐個執行每個進程,主線程會等到子線程全部執行完畢,在退出</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">run()</td>
<td class="org-left">可以重寫類時,重寫run方法,起到自定義線程類的作用</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">setDaemon(True)</td>
<td class="org-left">設置為守護進程</td>
</tr>
</tbody>
</table>
<a id="orgc9bc3d0"></a>
線程常用方法
<a id="orgd730348"></a>
普通創建方式
import threading
import time
def run(n):
print("task", n)
sleep(1)
t1 = threading.Thread(target=run, args=('t1',))
t2 = threading.Thread(target=run, args=('t2',))
t3 = threading.Thread(target=run, args=('t3',))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
<a id="orge37d9b1"></a>
重寫類,來實現啟動線程
class MyThread(threading.Thread):
def __init__(self, n):
super().__init__()
self.n = n
def run(self, self.n):
print("task", n)
time.sleep(1)
print("1s")
time.sleep(1)
print("2s")
if __name__ == "__main__":
t1 = MyThread("one")
t2 = MyThread("two")
t1.start()
t2.start()
<a id="orgb228d0f"></a>
其他方法
- threading.currentthread(): 輸出當前線程的名稱
- threading.activecount(): 輸出當前活躍線程的個數
- t.setDaemon(True): 設置當前線程為守護進程,只要主進程一退出,子進程也隨之退出, 必須在調用start()之前使用,否則會報RuntimeError的錯誤
<a id="orgb8fbe80"></a>
GIL
1.GIL全稱為Global Interpreter Lock(全局解釋器鎖),全局解釋器鎖的存在,保證了同一進程中,只能有一個線程正在運行,所以python多線程編程效率并沒有那么高,
通過使用多進程的方式,能夠有效地提升效率。python2中GIL是通過ticks來計數,計數值到達100,則解釋器,就會釋放,但是在python3中,GIL是通過計時來實現了,只要時間一到,不管任務完沒完成,都會收回
GIL(這種情況下,對cpu密集型運算更有利了一些,但依然問題很大),這種情況下,python對cpu密集型任務并不友好,而對io密集型任務比較友好。
2.GIL在cpython才存在,jpython和pypy中是沒用GIL,因為cpython調用的是c語言的原生線程
<a id="org7c0db1d"></a>
python多線程的工作流程
ipython使用多線程的時候,是使用c語言原生線程
1.拿到公共數據
2.申請GIL
3.python解釋器調用os原生線程
4.os執行cpu運算
5.時間到了收回GIL,無論是否完成任務
6.如此循環以上過程
7.當其他線程完成后,就繼續執行之前的線程(從之前線程執行的上下文處開始執行),每個線程都執行自己的運算,時間到了就切換線程
<a id="org7bb53a5"></a>
線程鎖
因為線程之間是公用同一份數據,所以當多個線程對同一數據進行操作時,容易產生臟數據,導致數據出現錯誤,所以出現了線程鎖的概念,在一個線程擁有該鎖時,其他線程不能進行操作,保證了,同一數據在同一時刻,
只有一個線程對其進行操作
<a id="orge4ca630"></a>
互斥鎖(mutex)
為了防止上述情況的發生,所以有了互斥鎖
import threading
# 實例化了一個鎖對象
lock = threading.Lock()
def run():
lock.acquire() # 獲得鎖,其他線程不能操作
print("hello world")
lock.release() # 釋放鎖,其他線程開始競爭
<a id="orga4ec85d"></a>
遞歸鎖
與上面的Lock用法類似,RLock可以嵌套,實現多個鎖
import threading
rlock = threading.RLock()
def run():
rlock.acquire()
rlock.acquire()
print("hello world")
rlock.release()
print("hello syk")
rlock.release()
<a id="orgf9efecd"></a>
信號量(BoundedSemphore)
互斥鎖只允許一個線程修改數據,信號量則允許多個線程同時修改,就比如廁所有三個坑,只有其中的坑空出來了,才能有人進去
import threading
semphore = threading.BoundedSemphore(5) # 表示最多有五個線程同時進行操作
def run(i):
semphore.acquire()
print("hello",i)
for i in range(20):
t = threading.Thread(target=run)
t.start()
while t.active_count != 1:
pass
else:
print("all thread has been done")
<a id="org9255bad"></a>
Event事件
python線程的事件主要是用于主線程控制其他線程的執行,event是一個簡單的事件同步對象,提供了一下幾種方法:
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
<colgroup>
<col class="org-left" />
<col class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">方法</th>
<th scope="col" class="org-left">注釋</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">set()</td>
<td class="org-left">將flag置為True</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">clear()</td>
<td class="org-left">將flag置為False</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">isset()</td>
<td class="org-left">是否將flag設置為True</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">wait()</td>
<td class="org-left">一直監聽flag,如果沒有監聽到,則一直為阻塞狀態</td>
</tr>
</tbody>
</table>
<a id="org034b100"></a>
條件(Condition類)
使得線程等待,滿足條件才將n個線程釋放
<a id="org70af908"></a>
定時器
定時器,指定n秒后執行操作
import threading
def hello():
print("hello world")
t = threading.Timer(1, hello)
t.start()
<a id="org06e9300"></a>
多進程
<a id="org4ad204e"></a>
什么是多進程
1.一個程序的執行實例就是一個進程,進程提供一個程序運行所需要的所有資源(進程本質上是資源的集合)
2.每一個進程都有自己的虛擬地址空間,可執行的代碼塊,操作系統接口,安全的上下文(記錄啟動該進程的用戶和權限),還有唯一的進程ID,
環境變量,優先級,最大最小的工作空間(內存空間)。
與進程相關的資源:
1.內存頁(統一進程下的線程共享同一內存空間)
2.文件描述符
3.安全憑證(啟動該進程的用戶ID)
<a id="org7331068"></a>
相關用法
from multiprocessing import Process
import time
def hello():
print("helo world")
if __name__ == "__main__":
p = Process(target=hello)
p.start()
p.join()
<a id="org5b3cf39"></a>
進程間的通信
由于進程間的數據是獨立的,所以進程間的通信可以通過Queue和Pipe來實現
<a id="org9d70940"></a>
使用Queue
from multiprocessing import Process, Queue
def q(p):
p.put([41, None, 'hello'])
if __name__ == "__main__":
p = Queue()
t = Process(target=q, args=(p,))
t.start()
print(q.get())
t.join()
<a id="org179cdd3"></a>
使用Pipe
Pipe的實質是數據的傳遞而不是數據的共享,在管道的兩頭都有一個recv和send方法,負責接受和發送,
但是如果兩端同時進行相同的操作,那么就會發生錯誤,破壞管道中的數據。
from multiprocessing import Process, Pipe
import time
def parent_pro(conn):
conn.send("hello world, my son")
conn.close()
def child_pro(conn):
data = conn.recv()
print(data)
conn.close()
if __name__ == "__main__":
parent_pipe, child_pipe = Pipe()
p1 = Process(target=parent_pro, args=(parent_pipe,))
p2 = Process(target=child_pro, args=(child_pipe,))
p1.start()
p2.start()
p1.join()
p2.join()
<a id="org475381f"></a>
進程池
因為多進程比較消耗內存空間,所以在使用多進程時,一般可以使用進程池(用來防止內存被大量消耗), (多線程不需要這種概念,多線程的啟動消耗比較小,但是多線程切換比較消耗cpu資源)
常用方法:
1.apply(): 同步執行(串行)
2.applyasync(): 異步執行(并行)
3.terminate(): 立即關閉進程池
4.join(): 主進程等待所有子進程執行完畢,必須在close()和terminate()之后
5.close(): 待所有進程執行完畢之后,關閉進程池