Python併發程式設計系列之多程式(multiprocessing)
正文
1 引言
本篇博文主要對Python中併發程式設計中的多程式相關內容展開詳細介紹,Python程式主要在multiprocessing模組中,本博文以multiprocessing種Process類為中心,透過例項程式碼對多程式設計到的程式間的同步機制、通訊機制、資料共享機制程式池進行介紹。
2 建立程式
建立程式有兩種方式,分別是透過定義函式的方式和透過定義類的方式。兩種方式建立程式都必須透過例項化Process類。
Process類引數如下:
1) group:這一引數值始終為None,尚未啟用,是為以後Python版本準備的
2) target:表示呼叫物件,即子程式要執行的任務
3) args:表示呼叫物件的位置引數元組,即target的位置引數,必須是元組,如args=(0,1,[1,2,3])
4) kwargs:表示呼叫物件的字典引數,kwargs={'name':'egon','age':18}
5) name:為子程式的名稱
另外,無論用那種方式建立程式都必須有“if __name__ == '__main__':”這一行程式碼作為程式入口,否則會報錯。
2.1 透過定義函式的方式建立程式
透過函式方式建立程式這一方法需在例項化程式例項時將函式名作為引數傳遞進去,函式的引數用一個tuple傳遞給程式例項:
from multiprocessing import Processimport timedef func (n): print("子程式開始執行:{}……".format(n)) time.sleep(1) print("子程式結束執行:{}……".format(n))if __name__ == '__main__':#建立程式執行一定要這一行程式碼 print("主程式開始執行……") p = Process(target=func,args=(10,)) # 註冊 p.start()# 啟動一個子程式 time.sleep(1)print("主程式結束執行……" )
2.2透過定義類的方式建立程式
用類的方式建立程式時,自定義的類必須必須繼承Process類,且必須覆寫run方法,所有功能程式碼都放在run方法中:
from multiprocessing import Processimport osclass MyProcess(Process): def run(self):#必須覆寫run方法 print('子程式:' , self.pid) if __name__=="__main__": p1 = MyProcess() p1.start() p2 = MyProcess() p2.start()print('主程式:', os.getpid())
上面程式碼沒有傳遞傳輸,如果要傳遞引數該怎麼做呢?那就要自定義構造方法了,但是在構造方法中,一定要先呼叫Process類的構造方法:
from multiprocessing import Processimport osclass MyProcess(Process): def __init__(self , arg1 , arg2): super().__init__() self.arg1 = arg1 self.arg2 = arg2 def run(self): print('子程式:' , self.pid , self.arg1 , self.arg2) if __name__=="__main__": p1 = MyProcess('我是arg1' , '我是arg2') p1.start() p2 = MyProcess('我是arg1' , '我是arg2') p2.start()print('主程式:', os.getpid())
3 Process的常用屬性和方法
Process類常用屬性如下:
1)daemon:預設值為False,如果設為True,則設為守護程式。
2)name:程式的名稱
3)pid:程式的pid
Process類常用方法如下:
1)start():啟動程式,並呼叫該子程式中的p.run() ;
2 )run():程式啟動時執行的方法,正是它去呼叫target指定的函式,我們自定義類的類中一定要實現該方法;
3 )terminate():強制終止程式p,不會進行任何清理操作,如果p建立了子程式,該子程式就成了殭屍程式,使用該方法需要特別小心這種情況。如果p還儲存了一個鎖那麼也將不會被釋放,進而導致死鎖;
4)is_alive():如果p仍然執行,返回True;
5)join([timeout]):主執行緒等待子程式終止
3.1守護程式:daemon
若要將一個程式設定為守護程式,在程式start之前,將其daemon屬性設定為True即可。但什麼是守護程式呢?我們透過如下程式碼來說明,我們要透過程式碼實現如下效果:主程式建立p1、p2程式,之後立馬沉睡5秒,p1程式設定為守護程式,程式p1每隔1秒列印一條語句,程式p2列印一條語句後立馬沉睡10秒。程式碼如下:
import timeimport osfrom multiprocessing import Processdef func(): i = 1 while True: time.sleep(1) print('{}--子程式p1正在執行,pid:{}'.format(i , os.getpid())) i+=1def func2(): print('子程式p2開始執行,pid:{}'.format(os.getpid())) time.sleep(10) print('子程式p2結束執行,pid:{}'.format(os.getpid()))if __name__=='__main__': print('主程式程式碼開始執行,pid:{}'.format(os.getpid())) p = Process(target=func) p.daemon = True # 設定為守護程式 p.start() p2 = Process(target=func2) p2.start() time.sleep(5)print('主程式程式碼執行完了,pid:{}'.format(os.getpid()))
執行結果:
主程式程式碼開始執行,pid:11608
子程式p2開始執行,pid:7260
1--子程式p1正在執行,pid:1060
2--子程式p1正在執行,pid:1060
3--子程式p1正在執行,pid:1060
4--子程式p1正在執行,pid:1060
主程式程式碼執行完了,pid:11608
子程式p2結束執行,pid:7260
從執行結果字面上似乎看不出什麼,因為區別在於輸出時間上。在主程式執行的那5秒時間(輸出“主程式程式碼執行完了,pid:11608”之前),p1程式確實可以每隔1秒輸出一條語句,但是主程式結束那5秒後,p1不在輸出,且在工作管理員中也可以檢視到,p1程式也已經死亡,主程式程式碼雖然執行完了,但依然存活,這時候p2程式依然還在沉睡,10秒後,p2程式列印“子程式p2結束執行,pid:7260”,然後主程式和p2程式一起死亡。
可以得出結果,守護程式依附於主程式程式碼,只要主程式程式碼執行完了,那麼無論守護程式程式碼是否執行完,守護程式都會結束。另外,守護程式不能建立自己的子程式。
3.2 程式終結於存活檢查:terminate()與is_alive()
terminate()與is_alive()都是由程式例項呼叫,分別用來終結一個程式、檢查一個程式是否依然存活:
import timeimport osfrom multiprocessing import Process def func(): i = 1 while True: time.sleep(1) print('{}--子程式p1正在執行,pid:{}'.format(i , os.getpid())) i+=1if __name__=='__main__': p = Process(target=func) p.start() time.sleep(3) p.terminate() # 終結程式p print(p.is_alive()) # 檢查p是否依然存活 time.sleep(1) print(p.is_alive())
輸出結果:
主程式程式碼開始執行,pid:13164
1--子程式p1正在執行,pid:8896
2--子程式p1正在執行,pid:8896
True
False
主程式程式碼執行完了,pid:13164
為什麼結束之後第一次呼叫is_alive()方法輸出的是True呢?因為terminate()方法終結一個程式時作業系統需要一定的響應時間,所以可能會有延遲。
3.3 join()方法
join方法功能是阻塞當前所在程式(例如下面的主程式),等到被join的程式(下面的程式p1)結束之後,回到當前程式繼續執行。
from multiprocessing import Processimport timedef func1 (): print("程式1開始執行……") for i in range(3): time.sleep(1) print("程式1執行了{}秒……".format(i+1)) print("程式1結束執行……") def func2 (): print("程式2開始執行……") for i in range(6): time.sleep(1) print("程式2執行了{}秒……".format(i+1)) print("程式2結束執行……") if __name__ == '__main__': print("主程式開始執行……") p1 = Process(target=func1) p2 = Process(target=func2) p1.start() p2.start() time.sleep(1) p1.join() # p2.join() print("主程式結束執行……" )
上述程式碼不進行join、分別是對程式1、程式2進行join的執行結果,發現,主程式會等待被join的程式執行完才會繼續執行join下面的程式碼。
4 程式間的同步控制
4.1 程式鎖:Lock
當多個程式對同一資源進行IO操作時,需要對資源“上鎖”,否則會出現意外結果。上鎖之後,同一件就只能有一個程式執行上鎖的程式碼塊。例如有一個txt檔案,裡面內容是一個數字10,我們要用多程式去讀取這個檔案的值,然後每讀一次,讓txt中的這個數字減1,不加鎖時程式碼如下:
from multiprocessing import Processfrom multiprocessing import Lockimport timeimport osdef func (): if os.path.exists('num.txt'): with open('num.txt' , 'r') as f: num = int(f.read()) num -= 1 time.sleep(1) with open('num.txt' , 'w') as f: f.write(str(num)) else: with open('num.txt' , 'w') as f: f.write('10')if __name__ == '__main__': print("主程式開始執行……") p_list = [] for i in range(10): p = Process(target=func) p_list.append(p) p.start() for p in p_list: p.join() with open('num.txt', 'r') as f: num = int(f.read()) print('最後結果為:{}'.format(num)) print("主程式結束執行……" )
輸出結果:
主程式開始執行……
最後結果為:8
主程式結束執行……
雖然我們用了10個程式讀取並修改txt檔案,但最後的值卻不是1。這正是多程式共同訪問資源造成混亂造成的。要達到預期結果,就要給資源上鎖:
from multiprocessing import Processfrom multiprocessing import Lockimport timeimport osdef func (lock): if os.path.exists('num.txt'): lock.acquire() with open('num.txt' , 'r') as f: num = int(f.read()) num -= 1 time.sleep(1) with open('num.txt' , 'w') as f: f.write(str(num)) lock.release() else: with open('num.txt' , 'w') as f: f.write('10')if __name__ == '__main__': print("主程式開始執行……") lock = Lock() p_list = [] for i in range(10): p = Process(target=func , args=(lock,)) p_list.append(p) p.start() for p in p_list: p.join() with open('num.txt', 'r') as f: num = int(f.read()) print('最後結果為:{}'.format(num)) print("主程式結束執行……" )
輸出結果:
主程式開始執行……
最後結果為:0
主程式結束執行……
果然,用了程式鎖之後獲得了預料中的結果。但是,如果你執行了上面兩塊程式碼你就會發現,加了鎖之後,程式明顯變慢了很多,因為程式成了序列的了,當然好處是資料安全有保證。
4.2 訊號量: Semaphore
鎖同時只允許一個執行緒更改資料,而訊號量是同時允許一定數量的程式更改資料 。假如有一下應用場景:有100個人吃飯,但只有一張餐桌,只允許做3個人,沒上桌的人不允許吃飯,已上桌吃完飯離座之後,下面的人才能搶佔桌子繼續吃飯,如果不用訊號量,肯定是100人一窩蜂一起吃飯:
from multiprocessing import Processimport timeimport randomdef fun(i): print('{}號顧客上座,開始吃飯'.format(i)) time.sleep(random.randint(3, 8)) print('{}號顧客吃完飯了,離座'.format(i))if __name__=='__main__': for i in range(100): p = Process(target=fun, args=(i,)) p.start()
輸出結果:
1號顧客上座,開始吃飯
2號顧客上座,開始吃飯
0號顧客上座,開始吃飯
3號顧客上座,開始吃飯
4號顧客上座,開始吃飯
5號顧客上座,開始吃飯
6號顧客上座,開始吃飯
7號顧客上座,開始吃飯
……
用了訊號量,實現了輪流吃飯,每次只有3個人吃飯:
from multiprocessing import Processimport timeimport randomfrom multiprocessing import Semaphoredef fun(i , sem): sem.acquire() print('{}號顧客上座,開始吃飯'.format(i)) time.sleep(random.randint(3, 8)) print('{}號顧客吃完飯了,離座'.format(i)) sem.release()if __name__=='__main__': sem = Semaphore(3) for i in range(10): p = Process(target=fun, args=(i,sem)) p.start()
輸出結果:
1號顧客上座,開始吃飯
0號顧客上座,開始吃飯
2號顧客上座,開始吃飯
1號顧客吃完飯了,離座
3號顧客上座,開始吃飯
2號顧客吃完飯了,離座
4號顧客上座,開始吃飯
0號顧客吃完飯了,離座
5號顧客上座,開始吃飯
……
上面只是輸出結果的一部分,不過已經看出,在同一時刻,只有3位顧客在吃飯(3個程式佔用資源),且只有在一位顧客離座之後才會有下一個顧客入座(一個程式結束對資源的佔用,下一個程式才能訪問資源)。事實上,Semaphore的作用也類似於鎖,只不過在鎖機制上新增了一個計數器,允許多個人擁有“鑰匙”。
4.3 事件:Event
python程式的事件用於主程式控制其他子程式的執行,Event類有如下幾個主要方法:
1)wait() 插入在程式中插入一個標記(flag)預設為 False,當 flag為False時,程式會停止執行進入阻塞狀態;
2)set() 使flag為True,程式會進入非阻塞狀態
3)clear() 使flag為False,程式會停止執行,進入阻塞狀態
4)is_set() 判斷flag 是否為True,是的話返回True,不是則返回False
有如下需求:獲取當前時間的秒數的個位數,如果小於5,則設定子程式阻塞,如果大於5則設定子程式非阻塞。程式碼如下:
from multiprocessing import Event, Processimport timefrom datetime import datetimedef func(e): print('子程式:開始執行……') while True: print('子程式:現在事件秒數是{}'.format(datetime.now().second)) e.wait() # 阻塞等待訊號 這裡插入了一個flag 預設為 False time.sleep(1) if __name__ == '__main__': e = Event() p = Process(target=func, args=(e,)) p.start() for i in range(10): s = int(str(datetime.now().second)[-1])#獲取當前秒數的個位數 if s < 5: print('子程式進入阻塞狀態') e.clear() # 使插入的flag為False 程式進入阻塞狀態 else: print('子程式取消阻塞狀態') e.set() # 使插入的flag為True,程式進入非阻塞狀態 time.sleep(1) e.set() time.sleep(3) p.terminate() print("主程式執行結束……")
輸出結果:
子程式取消阻塞狀態
子程式:開始執行……
子程式:現在事件秒數是58
子程式取消阻塞狀態
子程式:現在事件秒數是59
子程式取消阻塞狀態
子程式:現在事件秒數是0
子程式進入阻塞狀態
子程式:現在事件秒數是1
子程式進入阻塞狀態
子程式進入阻塞狀態
子程式進入阻塞狀態
子程式進入阻塞狀態
子程式取消阻塞狀態
子程式取消阻塞狀態
子程式:現在事件秒數是6
子程式:現在事件秒數是7
子程式:現在事件秒數是8
子程式:現在事件秒數是9
主程式執行結束……
5程式間通訊
5.1程式佇列:Queue
常用方法:
get( [ block [ ,timeout ] ] ) :返回q中的一個專案。如果q為空,此方法將阻塞,直到佇列中有專案可用為止。block用於控制阻塞行為,預設為True. 如果設定為False,將引發Queue.Empty異常(定義在Queue模組中)。timeout是可選超時時間,用在阻塞模式中。如果在制定的時間間隔內沒有專案變為可用,將引發Queue.Empty異常。
get_nowait( ) :同get(False)方法。
put(item [, block [,timeout ] ] ) :將item放入佇列。如果佇列已滿,此方法將阻塞至有空間可用為止。block控制阻塞行為,預設為True。如果設定為False,將引發Queue.Empty異常(定義在Queue庫模組中)。timeout指定在阻塞模式中等待可用空間的時間長短。超時後將引發Queue.Full異常。
qsize() :返回佇列中目前專案的正確數量。此函式的結果並不可靠,因為在返回結果和在稍後程式中使用結果之間,佇列中可能新增或刪除了專案。在某些系統上,此方法可能引發NotImplementedError異常。
empty() :如果呼叫此方法時佇列為空,返回True。如果其他程式或執行緒正在往佇列中新增專案,結果是不可靠的。也就是說,在返回和使用結果之間,佇列中可能已經加入新的專案。
full() :如果q已滿,返回為True. 由於執行緒的存在,結果也可能是不可靠的。
close() :關閉佇列,防止佇列中加入更多資料。呼叫此方法時,後臺執行緒將繼續寫入那些已入佇列但尚未寫入的資料,但將在此方法完成時馬上關閉。如果佇列被垃圾收集,將自動呼叫此方法。關閉佇列不會在佇列使用者中生成任何型別的資料結束訊號或異常。例如,如果某個使用者正被阻塞在get()操作上,關閉生產者中的佇列不會導致get()方法返回錯誤。
cancel_join_thread():不會再程式退出時自動連線後臺執行緒。這可以防止join_thread()方法阻塞。
join_thread():連線佇列的後臺執行緒。此方法用於在呼叫close()方法後,等待所有佇列項被消耗。預設情況下,此方法由不是佇列的原始建立者的所有程式呼叫。呼叫cancel_join_thread()方法可以禁止這種行為。
from multiprocessing import Processfrom multiprocessing import Queueimport randomimport os# 向queue中輸入資料的函式def inputQ(queue ): info = random.randint(1,100) queue.put(info) print('程式{}往佇列中存了一個資料:{}'.format(os.getpid() , info))# 向queue中輸出資料的函式def outputQ(queue): info = queue.get() print ('程式{}從佇列中取出一個資料:{}'.format(os.getpid() , info))if __name__ == '__main__': queue = Queue(5) lst_1 = [] lst_2 = [] for i in range(3): process = Process(target=inputQ,args=(queue,)) process.start() lst_1.append(process) # 輸出程式 for i in range(2): process = Process(target=outputQ ,args=(queue,)) process.start() lst_1.append(process) for p in lst_2: p.join() for p in lst_2: p.join()
另外,在使用程式池Pool時,使用Queue會出錯,需要使用Manager.Queue。
5.2 管道:Pipe
建立管道方法:
Pipe([duplex]):在程式之間建立一條管道,並返回元組(conn1,conn2),其中conn1,conn2表示管道兩端的連線物件,強調一點:必須在產生Process物件之前產生管道。dumplex:預設管道是全雙工的,如果將duplex射成False,conn1只能用於接收,conn2只能用於傳送。
conn1.recv():接收conn2.send(obj)傳送的物件。如果沒有訊息可接收,recv方法會一直阻塞。如果連線的另外一端已經關閉,那麼recv方法會丟擲EOFError。
conn1.send(obj):透過連線傳送物件。obj是與序列化相容的任意物件
conn1.close():關閉連線。如果conn1被垃圾回收,將自動呼叫此方法
conn1.fileno():返回連線使用的整數檔案描述符
conn1.poll([timeout]):如果連線上的資料可用,返回True。timeout指定等待的最長時限。如果省略此引數,方法將立即返回結果。如果將timeout射成None,操作將無限期地等待資料到達。
conn1.recv_bytes([maxlength]):接收c.send_bytes()方法傳送的一條完整的位元組訊息。maxlength指定要接收的最大位元組數。如果進入的訊息,超過了這個最大值,將引發IOError異常,並且在連線上無法進行進一步讀取。如果連線的另外一端已經關閉,再也不存在任何資料,將引發EOFError異常。
conn.send_bytes(buffer [, offset [, size]]):透過連線傳送位元組資料緩衝區,buffer是支援緩衝區介面的任意物件,offset是緩衝區中的位元組偏移量,而size是要傳送位元組數。結果資料以單條訊息的形式發出,然後呼叫c.recv_bytes()函式進行接收
conn1.recv_bytes_into(buffer [, offset]):接收一條完整的位元組訊息,並把它儲存在buffer物件中,該物件支援可寫入的緩衝區介面(即bytearray物件或類似的物件)。offset指定緩衝區中放置訊息處的位元組位移。返回值是收到的位元組數。如果訊息長度大於可用的緩衝區空間,將引發BufferTooShort異常。
from multiprocessing import Process, Pipedef f(conn): conn.send('主程式,你好呀!') # 傳送資料給主程式 print('子程式收到主程式發來的資料:{}'.format(conn.recv())) conn.close() # 關閉if __name__ == '__main__': parent_conn, child_conn = Pipe() #Pipe是一個函式,返回的是一個元組 p = Process(target=f, args=(child_conn,)) # 建立一個子程式 p.start() print("主程式收到子程式發來的資料:{}".format(parent_conn.recv())) parent_conn.send('子程式,你好啊!') # 傳送資料給子程式p.join()
6 程式間的資料共享
6.1 程式之間的資料隔離
程式間的變數是無法共享的,就算是全域性變數也不行:
from multiprocessing import Processimport osdef func(): global n n = 10 print('子程式pid:{},n:{}'.format(os.getppid() , n))if __name__=='__main__': n=100 print('主程式pid:{},n:{}'.format(os.getppid(), n)) p = Process(target=func) p.start() p.join() print('主程式中輸出n:{}'.format(n))
但是,如果建成間要進行資料共享,要怎麼做呢?
6.2 Manager
程式間的資料共享還可以透過Manager
import osfrom multiprocessing import Manager,Process# 定義了一個foo函式,接收一個字典和一個列表def foo(d, l): # 字典和列表都放程式ID d[os.getpid()] = os.getpid() l.append(os.getpid())if __name__ == '__main__': # 生成Manager物件 manager = Manager() d = manager.dict() l = manager.list(range(3)) # 10個程式分別join p_list = [] for i in range(10): p = Process(target=foo, args=(d, l)) p.start() p_list.append(p) for res in p_list: res.join() # 列印字典和列表 print(d) print(l)
7 程式池
為什麼要用程式池呢?如果我們有幾百上千個任務需要自行,那麼按照之前的做法,我們就要建立幾百上千個程式,每一個程式都要佔用一定的記憶體空間,程式間的切換也費時,系統開銷很大,而且,難道這成千上百個程式能同時併發執行的有幾個呢?其實也就那麼幾個子,所以,根本沒必要建立那麼多程式。那麼怎麼辦呢?那就建立程式池。程式池裡有固定數量的程式,每次執行任務時都從程式池中取出一個空閒程式來執行,如果任務數量超過程式池中程式數量,那麼就等待已經在執行的任務結束之後,有程式空閒之後再執行,也就是說,同一時間,只有固定數量的程式在執行,這樣對作業系統得壓力也不會太大,效率也得到保證。
import os,time,randomfrom multiprocessing import Pooldef func1(n): print('任務{}開始執行,程式為:{}'.format(n,os.getpid())) time.sleep(random.randint(1,4)) print('任務{}結束執行,程式為:{}'.format(n,os.getpid()))if __name__ == '__main__': p=Pool(3) #c建立一個程式池,裡面有三個程式 for i in range(10): res=p.apply(func1,args=(i,))
輸出結果:
任務0開始執行,程式為:14380
任務0結束執行,程式為:14380
任務1開始執行,程式為:14772
任務1結束執行,程式為:14772
任務2開始執行,程式為:10972
任務2結束執行,程式為:10972
任務3開始執行,程式為:14380
任務3結束執行,程式為:14380
任務4開始執行,程式為:14772
任務4結束執行,程式為:14772
可以看出,自始至終都只有3個程式在執行任務,但這些任務都是被同步執行的,如果要非同步執行呢:
import os,time,randomfrom multiprocessing import Pool def func1(n): print('任務{}開始執行,程式為:{}'.format(n,os.getpid())) time.sleep(random.randint(1,4)) print('任務{}結束執行,程式為:{}'.format(n,os.getpid()))if __name__ == '__main__': p=Pool(3) #c建立一個程式池,裡面有三個程式 for i in range(5): res=p.apply_async(func1,args=(i,)) p.close()#一定要關閉 p.join()#一定要使用join,不然程式池裡的程式沒來得及執行,主程式結束了,子程式也都跟著結束。
每個任務其實也都可以有返回值:
from multiprocessing import Pooldef func1(n): return n**2if __name__ == '__main__': p=Pool(3) result_lst = [] for i in range(5): result_lst.append(p.apply_async(func1,args=(i,)))#非同步 p.close() p.join() for result in result_lst: print(result.get())#如果是同步,就不用get了,直接用result獲取
8 總結
至此,Python併發程式設計中多程式部分就總結完了,花了幾天時間,參考了很多資料。
參考資料:
http://www.cnblogs.com/Eva-J/articles/8253549.html
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2310/viewspace-2818952/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Python分享之多程式初步 (multiprocessing包)Python
- Python分享之多程式探索 (multiprocessing包)Python
- 41、併發程式設計之多程式實操篇程式設計
- python併發程式設計之多執行緒理論部分Python程式設計執行緒
- 併發程式設計之多執行緒基礎程式設計執行緒
- Python併發程式設計Python程式設計
- java併發程式設計系列:java併發程式設計背景知識Java程式設計
- python-併發程式設計Python程式設計
- 併發程式設計之多執行緒執行緒安全程式設計執行緒
- 43、併發程式設計之多執行緒實操篇程式設計執行緒
- 42、併發程式設計之多執行緒理論篇程式設計執行緒
- Python併發程式設計之從效能角度來初探併發程式設計(一)Python程式設計
- 突擊併發程式設計JUC系列-併發工具 Semaphore程式設計
- PHP回顧之多程式程式設計PHP程式設計
- 併發程式設計程式設計
- Python_非同步程式設計-併發程式設計-協程和futurePython非同步程式設計
- Java併發程式設計系列之Semaphore詳解Java程式設計
- java併發程式設計系列:wait/notify機制Java程式設計AI
- Python程式專題2:multiprocessing建立程式Python
- python併發程式設計之程式1(守護程式,程式鎖,程式佇列)Python程式設計佇列
- 玩轉 PHP 網路程式設計全套之多程式程式設計PHP程式設計
- java 併發程式設計Java程式設計
- 併發程式設計—— LinkedTransferQueue程式設計
- 併發程式設計(ReentrantLock)程式設計ReentrantLock
- Go 併發程式設計Go程式設計
- golang併發程式設計Golang程式設計
- Golang 併發程式設計Golang程式設計
- 併發程式設計 synchronized程式設計synchronized
- 併發程式設計(四)程式設計
- 併發程式設計(二)程式設計
- Java併發程式設計Java程式設計
- 併發程式設計13程式設計
- java併發程式設計系列:牛逼的AQS(上)Java程式設計AQS
- java併發程式設計系列:牛逼的AQS(下)Java程式設計AQS
- Go 併發程式設計 - 併發安全(二)Go程式設計
- Java併發程式設計 - 第十一章 Java併發程式設計實踐Java程式設計
- 【Java併發程式設計】一、為什麼需要學習併發程式設計?Java程式設計
- python中socket+multiprocessing多程式Python