python中多線程與多進程的區別和聯繫

python多線程實現
python多線程和多進程的區別
python多線程爬蟲
python多線程坑
python多線程可以在windows下實現嗎
python多線程併發
python多線程變量共享
python多線程是併發還是並行
python多線程實例
python多線程condition

Python中多進程與多線程的區別有:線程需要在進程中執行,一個進程可包含多個線程;進程可共享同個地址空間而進程共享物理地址,線程創建簡單,進程需要對父進程克隆等等

今天將要分享的是Python中多進程與多線程的相關知識及區別,接下來將在文章中具體介紹,具有一定的參考價值,希望對大家有所幫助

更多的詳細內容學習點我

【推薦課程:Python教程

線程的概念:

線程是操作系統中進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程可以有多個線程,每條線程可以同時執行不同的任務。一個線程可以看作一個cpu執行時所需要的一串指令

多線程

在Python的標準庫中提供了兩個模塊:_thread和threading,_thread是低級模塊不支持守護線程,當主線程退出時,所有子線程都會被強行退出。而threading是高級模塊,用於對_thread進行了封裝支持守護線程。在大多數情況下我們只需要使用threading這個高級模塊即可。

進程的概念:

進程指的是一個程序在給定數據集合上的一次執行過程,是系統進行資源分配和運行調用的獨立單位。也就是每一個應用程序都有一個自己的進程。進程在啓動時都會最先產生一個線程,這個線程被稱爲主線程,然後再有主線程創建其他子線程

多進程:

多進程是multiprocessing模塊提供遠程與本地的併發,在一個multiprocessing庫的使用場景下,所有的子進程都是由一個父進程啓動來的,這個父進程成爲madter進程,它會管理一系列的對象狀態,一旦這個進程退出,子進程很可能處於一個不穩定的狀態,所以這個父進程儘量要少做事來保持其穩定性

線程與進程的區別

(1)線程必須在某個進程中執行。一個進程可包含多個線程,並且只有一個主線程。

(2)多線程共享同個地址空間、打開的文件以及其他資源。而多進程共享物理內存、磁盤、打印機以及其他資源。

(3)線程幾乎不佔資源,系統開銷少,切換速度快,而且同個進程中的多個線程可以實現數據共享,而進程之間是不可共享的

(4)新線程的創建很簡單而新進程的創建需要對父進程進行克隆

(5)一個線程可以控制和操作同一進程裏的其他線程;但是進程只能操作子進程

總結:以上就是本篇文章的全部內容了,希望對大家有所幫助。

以上就是python中多線程與多進程的區別的詳細內容

個人一直覺得對學習任何知識而言,概念是相當重要的。掌握了概念和原理,細節可以留給實踐去推敲。掌握的關鍵在於理解,通過具體的實例和實際操作來感性的體會概念和原理可以起到很好的效果。本文通過一些具體的例子簡單介紹一下python的多線程和多進程,後續會寫一些進程通信和線程通信的一些文章。

python多線程

python中提供兩個標準庫thread和threading用於對線程的支持,python3中已放棄對前者的支持,後者是一種更高層次封裝的線程庫,接下來均以後者爲例。

創建線程

python中有兩種方式實現線程:

1.實例化一個threading.Thread的對象,並傳入一個初始化函數對象(initial function )作爲線程執行的入口;

2.繼承threading.Thread,並重寫run函數;

方式1:創建threading.Thread對象

?

1

2

3

4

5

6

7

8

9

10

11

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")

結果:

?更多的詳細內容學習點我​​​​​​​

1

2

3

This is main function

This is thread 2 running....

This is thread 1 running....

方式2:繼承threading.Thread,並重寫run

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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")

執行結果同方式1.

threading.Thread

上面兩種方法本質上都是直接或者間接使用threading.Thread類

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

關聯上面兩種創建線程的方式:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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")

結果:

?

1

2

3

This is main function

This is thread 1 running....@target

This is thread 2 <a href="mailto:running....@target" rel="external nofollow">running....@target</a>

上面這段代碼說明:

1.兩種方式創建線程,指定的參數最終都會傳給threading.Thread類;

2.傳給線程的目標函數是在基類Thread的run函數體中被調用的,如果run沒有被重寫的話。

threading模塊的一些屬性和方法可以參照官網,這裏重點介紹一下threading.Thread對象的方法

下面是threading.Thread提供的線程對象方法和屬性:

  • start():創建線程後通過start啓動線程,等待CPU調度,爲run函數執行做準備;
  • run():線程開始執行的入口函數,函數體中會調用用戶編寫的target函數,或者執行被重載的run函數;
  • join([timeout]):阻塞掛起調用該函數的線程,直到被調用線程執行完成或超時。通常會在主線程中調用該方法,等待其他線程執行完成。
  • name、getName()&setName():線程名稱相關的操作;
  • ident:整數類型的線程標識符,線程開始執行前(調用start之前)爲None;
  • isAlive()、is_alive():start函數執行之後到run函數執行完之前都爲True;
  • daemon、isDaemon()&setDaemon():守護線程相關;

這些是我們創建線程之後通過線程對象對線程進行管理和獲取線程信息的方法。

多線程執行

在主線程中創建若線程之後,他們之間沒有任何協作和同步,除主線程之外每個線程都是從run開始被執行,直到執行完畢。

join

我們可以通過join方法讓主線程阻塞,等待其創建的線程執行完成。

?

1

2

3

4

5

6

7

8

9

10

11

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() # 當前線程阻塞,等待t1線程執行完成

 print("This is main function at:%s" % time.time())

結果:

?

1

2

3

This is thread 1 running....at: 1564906617.43

This is thread 1 is finished! at: 1564906618.43

This is main function at:1564906618.43

如果不加任何限制,當主線程執行完畢之後,當前程序並不會結束,必須等到所有線程都結束之後才能結束當前進程。

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

?

1

2

3

This is thread 1 running....at: 1564906769.52

This is main function at:1564906769.52

This is thread 1 is finished! at: 1564906770.52

可以通過將創建的線程指定爲守護線程(daemon),這樣主線程執行完畢之後會立即結束未執行完的線程,然後結束程序。

deamon守護線程

?

1

2

3

4

5

6

7

8

9

10

11

12

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()  # 當前線程阻塞,等待t1線程執行完成

  print("This is main function at:%s" % time.time())

結果:

?

1

2

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

This is main function at:1564906847.85

python多進程

相比較於threading模塊用於創建python多線程,python提供multiprocessing用於創建多進程。先看一下創建進程的兩種方式。

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

創建進程

創建進程的方式和創建線程的方式類似:

1.實例化一個multiprocessing.Process的對象,並傳入一個初始化函數對象(initial function )作爲新建進程執行入口;

2.繼承multiprocessing.Process,並重寫run函數;

方式1:

?

1

2

3

4

5

6

7

8

9

10

11

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())

結果:

?

1

2

3

Process name: subprocess, pid: 4888

subprocess pid: 4888

current process pid: 9912

方式2:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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())

這裏可以思考一下,如果像多線程一樣,存在一個全局的變量share_data,不同進程同時訪問share_data會有問題嗎?

由於每一個進程擁有獨立的內存地址空間且互相隔離,因此不同進程看到的share_data是不同的、分別位於不同的地址空間,同時訪問不會有問題。這裏需要注意一下。

Subprocess模塊

既然說道了多進程,那就順便提一下另一種創建進程的方式。

python提供了Sunprocess模塊可以在程序執行過程中,調用外部的程序。

如我們可以在python程序中打開記事本,打開cmd,或者在某個時間點關機:

?

1

2

3

4

5

6

>>> import subprocess

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

<subprocess.Popen object at 0x0339F550>

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

<subprocess.Popen object at 0x03262B70>

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

或者使用ping測試一下網絡連通性:

?

1

2

3

4

5

6

7

8

9

10

11

>>> res = subprocess.Popen(['ping''www.cnblogs.com'], stdout=subprocess.PIPE).communicate()[0]

>>> print res

正在 Ping www.cnblogs.com [101.37.113.127] 具有 32 字節的數據:

來自 101.37.113.127 的回覆: 字節=32 時間=1ms TTL=91 來自 101.37.113.127 的回覆: 字節=32 時間=1ms TTL=91

來自 101.37.113.127 的回覆: 字節=32 時間=1ms TTL=91

來自 101.37.113.127 的回覆: 字節=32 時間=1ms TTL=91

101.37.113.127 的 Ping 統計信息:

  數據包: 已發送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),

往返行程的估計時間(以毫秒爲單位):

  最短 = 1ms,最長 = 1ms,平均 = 1ms

python多線程與多進程比較

先來看兩個例子:

開啓兩個python線程分別做一億次加一操作,和單獨使用一個線程做一億次加一操作:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

def tstart(arg):

  var = 0

  for 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))

結果:

?

1

2

Two thread cost time: 20.6570000648

Main thread cost time: 2.52800011635

上面的例子如果只開啓t1和t2兩個線程中的一個,那麼運行時間和主線程基本一致。這個後面會解釋原因。

使用兩個進程進行上面的操作:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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))

結果:

?

1

2

Two process cost time: 2.91599988937

Current process cost time: 2.52400016785

對比分析

雙進程並行執行和單進程執行相同的運算代碼,耗時基本相同,雙進程耗時會稍微多一些,可能的原因是進程創建和銷燬會進行系統調用,造成額外的時間開銷。

但是對於python線程,雙線程並行執行耗時比單線程要高的多,效率相差近10倍。如果將兩個並行線程改成串行執行,即:

?

1

2

3

4

5

6

t1.start()

  t1.join()

  t2.start()

  t2.join()

  #Two thread cost time: 5.12199997902

  #Main thread cost time: 2.54200005531

可以看到三個線程串行執行,每一個執行的時間基本相同。

本質原因雙線程是併發執行的,而不是真正的並行執行。原因就在於GIL鎖。

GIL鎖

提起python多線程就不得不提一下GIL(Global Interpreter Lock 全局解釋器鎖),這是目前佔統治地位的python解釋器CPython中爲了保證數據安全所實現的一種鎖。不管進程中有多少線程,只有拿到了GIL鎖的線程纔可以在CPU上運行,即時是多核處理器。對一個進程而言,不管有多少線程,任一時刻,只會有一個線程在執行。對於CPU密集型的線程,其效率不僅僅不高,反而有可能比較低。python多線程比較適用於IO密集型的程序。對於的確需要並行運行的程序,可以考慮多進程。

多線程對鎖的爭奪,CPU對線程的調度,線程之間的切換等均會有時間開銷。

線程與進程區別

下面簡單的比較一下線程與進程

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

線程和進程的上下文切換

進程切換過程切換牽涉到非常多的東西,寄存器內容保存到任務狀態段TSS,切換頁表,堆棧等。簡單來說可以分爲下面兩步:

頁全局目錄切換,使CPU到新進程的線性地址空間尋址;
切換內核態堆棧和硬件上下文,硬件上下文包含CPU寄存器的內容,存放在TSS中;
線程運行於進程地址空間,切換過程不涉及到空間的變換,只牽涉到第二步;

使用多線程還是多進程?

CPU密集型:程序需要佔用CPU進行大量的運算和數據處理;

I/O密集型:程序中需要頻繁的進行I/O操作;例如網絡中socket數據傳輸和讀取等;

由於python多線程並不是並行執行,因此較適合與I/O密集型程序,多進程並行執行適用於CPU密集型程序

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