日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

python多線(xiàn)程與多進(jìn)程及其區(qū)別詳解

系統(tǒng) 1715 0

前言

個(gè)人一直覺(jué)得對(duì)學(xué)習(xí)任何知識(shí)而言,概念是相當(dāng)重要的。掌握了概念和原理,細(xì)節(jié)可以留給實(shí)踐去推敲。掌握的關(guān)鍵在于理解,通過(guò)具體的實(shí)例和實(shí)際操作來(lái)感性的體會(huì)概念和原理可以起到很好的效果。本文通過(guò)一些具體的例子簡(jiǎn)單介紹一下python的多線(xiàn)程和多進(jìn)程,后續(xù)會(huì)寫(xiě)一些進(jìn)程通信和線(xiàn)程通信的一些文章。

python多線(xiàn)程

python中提供兩個(gè)標(biāo)準(zhǔn)庫(kù)thread和threading用于對(duì)線(xiàn)程的支持,python3中已放棄對(duì)前者的支持,后者是一種更高層次封裝的線(xiàn)程庫(kù),接下來(lái)均以后者為例。

創(chuàng)建線(xiàn)程

python中有兩種方式實(shí)現(xiàn)線(xiàn)程:

1.實(shí)例化一個(gè)threading.Thread的對(duì)象,并傳入一個(gè)初始化函數(shù)對(duì)象(initial function )作為線(xiàn)程執(zhí)行的入口;

2.繼承threading.Thread,并重寫(xiě)run函數(shù);

方式1:創(chuàng)建threading.Thread對(duì)象

            
import threading
import time
def tstart(arg):
 time.sleep(0.5)
 print("%s running...." % arg)
if __name__ == '__main__':
 t1 = threading.Thread(target=tstart, args=('This is thread 1',))
 t2 = threading.Thread(target=tstart, args=('This is thread 2',))
 t1.start()
 t2.start()
 print("This is main function")
          

結(jié)果:

            
This is main function
This is thread 2 running....
This is thread 1 running....
          

方式2:繼承threading.Thread,并重寫(xiě)run

            
import threading
import time
class CustomThread(threading.Thread):
 def __init__(self, thread_name):
  # step 1: call base __init__ function
  super(CustomThread, self).__init__(name=thread_name)
  self._tname = thread_name
 def run(self):
  # step 2: overide run function
  time.sleep(0.5)
  print("This is %s running...." % self._tname)
if __name__ == "__main__":
 t1 = CustomThread("thread 1")
 t2 = CustomThread("thread 2")
 t1.start()
 t2.start()
 print("This is main function")
          

執(zhí)行結(jié)果同方式1.

threading.Thread

上面兩種方法本質(zhì)上都是直接或者間接使用threading.Thread類(lèi)

threading.Thread(group=None, target=None, name=None, args=(), kwargs={})

關(guān)聯(lián)上面兩種創(chuàng)建線(xiàn)程的方式:

            
import threading
import time
class CustomThread(threading.Thread):
 def __init__(self, thread_name, target = None):
  # step 1: call base __init__ function
  super(CustomThread, self).__init__(name=thread_name, target=target, args = (thread_name,))
  self._tname = thread_name
 def run(self):
  # step 2: overide run function
  # time.sleep(0.5)
  # print("This is %s running....@run" % self._tname)
  super(CustomThread, self).run()
def target(arg):
 time.sleep(0.5)
 print("This is %s running....@target" % arg)
if __name__ == "__main__":
 t1 = CustomThread("thread 1", target)
 t2 = CustomThread("thread 2", target)
 t1.start()
 t2.start()
 print("This is main function")
          

結(jié)果:

            
This is main function
This is thread 1 running....@target
This is thread 2 running....@target
          

上面這段代碼說(shuō)明:

1.兩種方式創(chuàng)建線(xiàn)程,指定的參數(shù)最終都會(huì)傳給threading.Thread類(lèi);

2.傳給線(xiàn)程的目標(biāo)函數(shù)是在基類(lèi)Thread的run函數(shù)體中被調(diào)用的,如果run沒(méi)有被重寫(xiě)的話(huà)。

threading模塊的一些屬性和方法可以參照官網(wǎng),這里重點(diǎn)介紹一下threading.Thread對(duì)象的方法

下面是threading.Thread提供的線(xiàn)程對(duì)象方法和屬性:

  • start():創(chuàng)建線(xiàn)程后通過(guò)start啟動(dòng)線(xiàn)程,等待CPU調(diào)度,為run函數(shù)執(zhí)行做準(zhǔn)備;
  • run():線(xiàn)程開(kāi)始執(zhí)行的入口函數(shù),函數(shù)體中會(huì)調(diào)用用戶(hù)編寫(xiě)的target函數(shù),或者執(zhí)行被重載的run函數(shù);
  • join([timeout]):阻塞掛起調(diào)用該函數(shù)的線(xiàn)程,直到被調(diào)用線(xiàn)程執(zhí)行完成或超時(shí)。通常會(huì)在主線(xiàn)程中調(diào)用該方法,等待其他線(xiàn)程執(zhí)行完成。
  • name、getName()&setName():線(xiàn)程名稱(chēng)相關(guān)的操作;
  • ident:整數(shù)類(lèi)型的線(xiàn)程標(biāo)識(shí)符,線(xiàn)程開(kāi)始執(zhí)行前(調(diào)用start之前)為None;
  • isAlive()、is_alive():start函數(shù)執(zhí)行之后到run函數(shù)執(zhí)行完之前都為T(mén)rue;
  • daemon、isDaemon()&setDaemon():守護(hù)線(xiàn)程相關(guān);

這些是我們創(chuàng)建線(xiàn)程之后通過(guò)線(xiàn)程對(duì)象對(duì)線(xiàn)程進(jìn)行管理和獲取線(xiàn)程信息的方法。

多線(xiàn)程執(zhí)行

在主線(xiàn)程中創(chuàng)建若線(xiàn)程之后,他們之間沒(méi)有任何協(xié)作和同步,除主線(xiàn)程之外每個(gè)線(xiàn)程都是從run開(kāi)始被執(zhí)行,直到執(zhí)行完畢。

join

我們可以通過(guò)join方法讓主線(xiàn)程阻塞,等待其創(chuàng)建的線(xiàn)程執(zhí)行完成。

            
import threading
import time
def tstart(arg):
 print("%s running....at: %s" % (arg,time.time()))
 time.sleep(1)
 print("%s is finished! at: %s" % (arg,time.time()))
if __name__ == '__main__':
 t1 = threading.Thread(target=tstart, args=('This is thread 1',))
 t1.start()
 t1.join() # 當(dāng)前線(xiàn)程阻塞,等待t1線(xiàn)程執(zhí)行完成
 print("This is main function at:%s" % time.time())
          

結(jié)果:

            
This is thread 1 running....at: 1564906617.43
This is thread 1 is finished! at: 1564906618.43
This is main function at:1564906618.43
          

如果不加任何限制,當(dāng)主線(xiàn)程執(zhí)行完畢之后,當(dāng)前程序并不會(huì)結(jié)束,必須等到所有線(xiàn)程都結(jié)束之后才能結(jié)束當(dāng)前進(jìn)程。

將上面程序中的t1.join()去掉,執(zhí)行結(jié)果如下:

            
This is thread 1 running....at: 1564906769.52
This is main function at:1564906769.52
This is thread 1 is finished! at: 1564906770.52
          

可以通過(guò)將創(chuàng)建的線(xiàn)程指定為守護(hù)線(xiàn)程(daemon),這樣主線(xiàn)程執(zhí)行完畢之后會(huì)立即結(jié)束未執(zhí)行完的線(xiàn)程,然后結(jié)束程序。

deamon守護(hù)線(xiàn)程

            
import threading
import time
def tstart(arg):
  print("%s running....at: %s" % (arg,time.time()))
  time.sleep(1)
  print("%s is finished! at: %s" % (arg,time.time()))
if __name__ == '__main__':
  t1 = threading.Thread(target=tstart, args=('This is thread 1',))
  t1.setDaemon(True)
  t1.start()
  # t1.join()  # 當(dāng)前線(xiàn)程阻塞,等待t1線(xiàn)程執(zhí)行完成
  print("This is main function at:%s" % time.time())
          

結(jié)果:

            
This is thread 1 running....at: 1564906847.85
This is main function at:1564906847.85
          

python多進(jìn)程

相比較于threading模塊用于創(chuàng)建python多線(xiàn)程,python提供multiprocessing用于創(chuàng)建多進(jìn)程。先看一下創(chuàng)建進(jìn)程的兩種方式。

The multiprocessing package mostly replicates the API of the threading module.  ―― python doc

創(chuàng)建進(jìn)程

創(chuàng)建進(jìn)程的方式和創(chuàng)建線(xiàn)程的方式類(lèi)似:

1.實(shí)例化一個(gè)multiprocessing.Process的對(duì)象,并傳入一個(gè)初始化函數(shù)對(duì)象(initial function )作為新建進(jìn)程執(zhí)行入口;

2.繼承multiprocessing.Process,并重寫(xiě)run函數(shù);

方式1:

            
from multiprocessing import Process 
import os, time
def pstart(name):
  # time.sleep(0.1)
  print("Process name: %s, pid: %s "%(name, os.getpid()))
if __name__ == "__main__": 
  subproc = Process(target=pstart, args=('subprocess',)) 
  subproc.start() 
  subproc.join()
  print("subprocess pid: %s"%subproc.pid)
  print("current process pid: %s" % os.getpid())
          

結(jié)果:

            
Process name: subprocess, pid: 4888 
subprocess pid: 4888
current process pid: 9912
          

方式2:

            
from multiprocessing import Process 
import os, time
class CustomProcess(Process):
  def __init__(self, p_name, target=None):
    # step 1: call base __init__ function()
    super(CustomProcess, self).__init__(name=p_name, target=target, args=(p_name,))

  def run(self):
    # step 2:
    # time.sleep(0.1)
    print("Custom Process name: %s, pid: %s "%(self.name, os.getpid()))
if __name__ == '__main__':
  p1 = CustomProcess("process_1")
  p1.start()
  p1.join()
  print("subprocess pid: %s"%p1.pid)
  print("current process pid: %s" % os.getpid())
          

這里可以思考一下,如果像多線(xiàn)程一樣,存在一個(gè)全局的變量share_data,不同進(jìn)程同時(shí)訪(fǎng)問(wèn)share_data會(huì)有問(wèn)題嗎?

由于每一個(gè)進(jìn)程擁有獨(dú)立的內(nèi)存地址空間且互相隔離,因此不同進(jìn)程看到的share_data是不同的、分別位于不同的地址空間,同時(shí)訪(fǎng)問(wèn)不會(huì)有問(wèn)題。這里需要注意一下。

Subprocess模塊

既然說(shuō)道了多進(jìn)程,那就順便提一下另一種創(chuàng)建進(jìn)程的方式。

python提供了Sunprocess模塊可以在程序執(zhí)行過(guò)程中,調(diào)用外部的程序。

如我們可以在python程序中打開(kāi)記事本,打開(kāi)cmd,或者在某個(gè)時(shí)間點(diǎn)關(guān)機(jī):

            
>>> import subprocess
>>> subprocess.Popen(['cmd'])

            
              
>>> subprocess.Popen(['notepad'])

              
                
>>> subprocess.Popen(['shutdown', '-p'])
              
            
          

或者使用ping測(cè)試一下網(wǎng)絡(luò)連通性:

            
>>> res = subprocess.Popen(['ping', 'www.cnblogs.com'], stdout=subprocess.PIPE).communicate()[0]
>>> print res
正在 Ping www.cnblogs.com [101.37.113.127] 具有 32 字節(jié)的數(shù)據(jù):
來(lái)自 101.37.113.127 的回復(fù): 字節(jié)=32 時(shí)間=1ms TTL=91 來(lái)自 101.37.113.127 的回復(fù): 字節(jié)=32 時(shí)間=1ms TTL=91
來(lái)自 101.37.113.127 的回復(fù): 字節(jié)=32 時(shí)間=1ms TTL=91
來(lái)自 101.37.113.127 的回復(fù): 字節(jié)=32 時(shí)間=1ms TTL=91

101.37.113.127 的 Ping 統(tǒng)計(jì)信息:
  數(shù)據(jù)包: 已發(fā)送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計(jì)時(shí)間(以毫秒為單位):
  最短 = 1ms,最長(zhǎng) = 1ms,平均 = 1ms
          

python多線(xiàn)程與多進(jìn)程比較

先來(lái)看兩個(gè)例子:

開(kāi)啟兩個(gè)python線(xiàn)程分別做一億次加一操作,和單獨(dú)使用一個(gè)線(xiàn)程做一億次加一操作:

            
def tstart(arg):
  var = 0
  for i in xrange(100000000):
    var += 1

if __name__ == '__main__':
  t1 = threading.Thread(target=tstart, args=('This is thread 1',))
  t2 = threading.Thread(target=tstart, args=('This is thread 2',))
  start_time = time.time()
  t1.start()
  t2.start()
  t1.join()
  t2.join()
  print("Two thread cost time: %s" % (time.time() - start_time))
  start_time = time.time()
  tstart("This is thread 0")
  print("Main thread cost time: %s" % (time.time() - start_time))
          

結(jié)果:

            
Two thread cost time: 20.6570000648
Main thread cost time: 2.52800011635
          

上面的例子如果只開(kāi)啟t1和t2兩個(gè)線(xiàn)程中的一個(gè),那么運(yùn)行時(shí)間和主線(xiàn)程基本一致。這個(gè)后面會(huì)解釋原因。

使用兩個(gè)進(jìn)程進(jìn)行上面的操作:

            
def pstart(arg):
  var = 0
  for i in xrange(100000000):
    var += 1
if __name__ == '__main__':
  p1 = Process(target = pstart, args = ("1", ))
  p2 = Process(target = pstart, args = ("2", ))
  start_time = time.time()
  p1.start()
  p2.start()
  p1.join()
  p2.join()
  print("Two process cost time: %s" % (time.time() - start_time))
  start_time = time.time()
  pstart("0")
  print("Current process cost time: %s" % (time.time() - start_time))
          

結(jié)果:

            
Two process cost time: 2.91599988937
Current process cost time: 2.52400016785
          

對(duì)比分析

雙進(jìn)程并行執(zhí)行和單進(jìn)程執(zhí)行相同的運(yùn)算代碼,耗時(shí)基本相同,雙進(jìn)程耗時(shí)會(huì)稍微多一些,可能的原因是進(jìn)程創(chuàng)建和銷(xiāo)毀會(huì)進(jìn)行系統(tǒng)調(diào)用,造成額外的時(shí)間開(kāi)銷(xiāo)。

但是對(duì)于python線(xiàn)程,雙線(xiàn)程并行執(zhí)行耗時(shí)比單線(xiàn)程要高的多,效率相差近10倍。如果將兩個(gè)并行線(xiàn)程改成串行執(zhí)行,即:

            
t1.start()
  t1.join()
  t2.start()
  t2.join()
  #Two thread cost time: 5.12199997902
  #Main thread cost time: 2.54200005531
          

可以看到三個(gè)線(xiàn)程串行執(zhí)行,每一個(gè)執(zhí)行的時(shí)間基本相同。

本質(zhì)原因雙線(xiàn)程是并發(fā)執(zhí)行的,而不是真正的并行執(zhí)行。原因就在于GIL鎖。

GIL鎖

提起python多線(xiàn)程就不得不提一下GIL(Global Interpreter Lock 全局解釋器鎖),這是目前占統(tǒng)治地位的python解釋器CPython中為了保證數(shù)據(jù)安全所實(shí)現(xiàn)的一種鎖。不管進(jìn)程中有多少線(xiàn)程,只有拿到了GIL鎖的線(xiàn)程才可以在CPU上運(yùn)行,即時(shí)是多核處理器。對(duì)一個(gè)進(jìn)程而言,不管有多少線(xiàn)程,任一時(shí)刻,只會(huì)有一個(gè)線(xiàn)程在執(zhí)行。對(duì)于CPU密集型的線(xiàn)程,其效率不僅僅不高,反而有可能比較低。python多線(xiàn)程比較適用于IO密集型的程序。對(duì)于的確需要并行運(yùn)行的程序,可以考慮多進(jìn)程。

多線(xiàn)程對(duì)鎖的爭(zhēng)奪,CPU對(duì)線(xiàn)程的調(diào)度,線(xiàn)程之間的切換等均會(huì)有時(shí)間開(kāi)銷(xiāo)。

線(xiàn)程與進(jìn)程區(qū)別

下面簡(jiǎn)單的比較一下線(xiàn)程與進(jìn)程

  • 進(jìn)程是資源分配的基本單位,線(xiàn)程是CPU執(zhí)行和調(diào)度的基本單位;
  • 通信/同步方式:
    • 進(jìn)程:
      • 通信方式:管道,F(xiàn)IFO,消息隊(duì)列,信號(hào),共享內(nèi)存,socket,stream流;
      • 同步方式:PV信號(hào)量,管程
    • 線(xiàn)程:
      • 同步方式:互斥鎖,遞歸鎖,條件變量,信號(hào)量
      • 通信方式:位于同一進(jìn)程的線(xiàn)程共享進(jìn)程資源,因此線(xiàn)程間沒(méi)有類(lèi)似于進(jìn)程間用于數(shù)據(jù)傳遞的通信方式,線(xiàn)程間的通信主要是用于線(xiàn)程同步。
  • CPU上真正執(zhí)行的是線(xiàn)程,線(xiàn)程比進(jìn)程輕量,其切換和調(diào)度代價(jià)比進(jìn)程要小;
  • 線(xiàn)程間對(duì)于共享的進(jìn)程數(shù)據(jù)需要考慮線(xiàn)程安全問(wèn)題,由于進(jìn)程之間是隔離的,擁有獨(dú)立的內(nèi)存空間資源,相對(duì)比較安全,只能通過(guò)上面列出的IPC(Inter-Process Communication)進(jìn)行數(shù)據(jù)傳輸;
  • 系統(tǒng)有一個(gè)個(gè)進(jìn)程組成,每個(gè)進(jìn)程包含代碼段、數(shù)據(jù)段、堆空間和棧空間,以及操作系統(tǒng)共享部分 ,有等待,就緒和運(yùn)行三種狀態(tài);
  • 一個(gè)進(jìn)程可以包含多個(gè)線(xiàn)程,線(xiàn)程之間共享進(jìn)程的資源(文件描述符、全局變量、堆空間等),寄存器變量和棧空間等是線(xiàn)程私有的;
  • 操作系統(tǒng)中一個(gè)進(jìn)程掛掉不會(huì)影響其他進(jìn)程,如果一個(gè)進(jìn)程中的某個(gè)線(xiàn)程掛掉而且OS對(duì)線(xiàn)程的支持是多對(duì)一模型,那么會(huì)導(dǎo)致當(dāng)前進(jìn)程掛掉;
  • 如果CPU和系統(tǒng)支持多線(xiàn)程與多進(jìn)程,多個(gè)進(jìn)程并行執(zhí)行的同時(shí),每個(gè)進(jìn)程中的線(xiàn)程也可以并行執(zhí)行,這樣才能最大限度的榨取硬件的性能;

線(xiàn)程和進(jìn)程的上下文切換

進(jìn)程切換過(guò)程切換牽涉到非常多的東西,寄存器內(nèi)容保存到任務(wù)狀態(tài)段TSS,切換頁(yè)表,堆棧等。簡(jiǎn)單來(lái)說(shuō)可以分為下面兩步:

頁(yè)全局目錄切換,使CPU到新進(jìn)程的線(xiàn)性地址空間尋址;
切換內(nèi)核態(tài)堆棧和硬件上下文,硬件上下文包含CPU寄存器的內(nèi)容,存放在TSS中;
線(xiàn)程運(yùn)行于進(jìn)程地址空間,切換過(guò)程不涉及到空間的變換,只牽涉到第二步;

使用多線(xiàn)程還是多進(jìn)程?

CPU密集型:程序需要占用CPU進(jìn)行大量的運(yùn)算和數(shù)據(jù)處理;

I/O密集型:程序中需要頻繁的進(jìn)行I/O操作;例如網(wǎng)絡(luò)中socket數(shù)據(jù)傳輸和讀取等;

由于python多線(xiàn)程并不是并行執(zhí)行,因此較適合與I/O密集型程序,多進(jìn)程并行執(zhí)行適用于CPU密集型程序;

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 疏附县| 吉首市| 克拉玛依市| 集贤县| 北碚区| 法库县| 珲春市| 鄱阳县| 绩溪县| 丹阳市| 新巴尔虎右旗| 阿荣旗| 黔江区| 新余市| 棋牌| 虞城县| 左权县| 沾益县| 临城县| 资源县| 军事| 修水县| 宁阳县| 都兰县| 临城县| 运城市| 滦南县| 沁水县| 克什克腾旗| 湄潭县| 互助| 江城| 西城区| 双桥区| 宝应县| 安顺市| 扎鲁特旗| 乌兰察布市| 大洼县| 新龙县| 太和县|