欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2019年08月08日 08:31:51   作者:alpha_panda  
這篇文章主要介紹了python多線程與多進(jìn)程及其區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

前言

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

python多線程

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

創(chuàng)建線程

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

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

2.繼承threading.Thread,并重寫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,并重寫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類

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

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

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)建線程,指定的參數(shù)最終都會(huì)傳給threading.Thread類;

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

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

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

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

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

多線程執(zhí)行

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

join

我們可以通過(guò)join方法讓主線程阻塞,等待其創(chuàng)建的線程執(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)前線程阻塞,等待t1線程執(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)主線程執(zhí)行完畢之后,當(dāng)前程序并不會(huì)結(jié)束,必須等到所有線程都結(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)建的線程指定為守護(hù)線程(daemon),這樣主線程執(zhí)行完畢之后會(huì)立即結(jié)束未執(zhí)行完的線程,然后結(jié)束程序。

deamon守護(hù)線程

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)前線程阻塞,等待t1線程執(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多線程,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)建線程的方式類似:

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

2.繼承multiprocessing.Process,并重寫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())

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

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

Subprocess模塊

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

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

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

>>> import subprocess
>>> subprocess.Popen(['cmd'])
<subprocess.Popen object at 0x0339F550>
>>> subprocess.Popen(['notepad'])
<subprocess.Popen object at 0x03262B70>
>>> 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多線程與多進(jìn)程比較

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

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

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

上面的例子如果只開啟t1和t2兩個(gè)線程中的一個(gè),那么運(yùn)行時(shí)間和主線程基本一致。這個(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)建和銷毀會(huì)進(jìn)行系統(tǒng)調(diào)用,造成額外的時(shí)間開銷。

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

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

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

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

GIL鎖

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

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

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

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

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

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

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

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

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

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

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

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

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

相關(guān)文章

  • Python+OpenCV 圖像邊緣檢測(cè)四種實(shí)現(xiàn)方法

    Python+OpenCV 圖像邊緣檢測(cè)四種實(shí)現(xiàn)方法

    本文主要介紹了通過(guò)OpenCV中Sobel算子、Schaar算子、Laplacian算子以及Canny分別實(shí)現(xiàn)圖像邊緣檢測(cè)并總結(jié)了四者的優(yōu)缺點(diǎn),感興趣的同學(xué)可以參考一下
    2021-11-11
  • Python操作PDF實(shí)現(xiàn)制作數(shù)據(jù)報(bào)告

    Python操作PDF實(shí)現(xiàn)制作數(shù)據(jù)報(bào)告

    Python操作PDF的庫(kù)有很多,比如PyPDF2、pdfplumber、PyMuPDF等等。本文將利用FPDF模塊操作PDF實(shí)現(xiàn)制作數(shù)據(jù)報(bào)告,感興趣的小伙伴可以嘗試一下
    2022-12-12
  • Python?Ajax爬蟲案例分享

    Python?Ajax爬蟲案例分享

    這篇文章主要介紹了Python?Ajax爬蟲案例分享,文章會(huì)從街拍鏈接里面爬取圖片結(jié)構(gòu),下面文章對(duì)正在學(xué)習(xí)的你有一定的幫助,需要的小伙伴可以參考一下
    2022-02-02
  • python 操作hive pyhs2方式

    python 操作hive pyhs2方式

    今天小編就為大家分享一篇python 操作hive pyhs2方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-12-12
  • 10款最好的Web開發(fā)的 Python 框架

    10款最好的Web開發(fā)的 Python 框架

    這篇文章主要介紹了10款最好的Web開發(fā)的 Python 框架,總結(jié)的都是非常常用的而且評(píng)價(jià)都非常不錯(cuò)的框架,需要的朋友可以參考下
    2015-03-03
  • 解決Python計(jì)算矩陣乘向量,矩陣乘實(shí)數(shù)的一些小錯(cuò)誤

    解決Python計(jì)算矩陣乘向量,矩陣乘實(shí)數(shù)的一些小錯(cuò)誤

    今天小編就為大家分享一篇解決Python計(jì)算矩陣乘向量,矩陣乘實(shí)數(shù)的一些小錯(cuò)誤,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-08-08
  • 基于Python實(shí)現(xiàn)開發(fā)釘釘通知機(jī)器人

    基于Python實(shí)現(xiàn)開發(fā)釘釘通知機(jī)器人

    在項(xiàng)目協(xié)同工作或自動(dòng)化流程完成時(shí),我們需要用一定的手段通知自己或他人。Telegram 非常好用,幾個(gè)步驟就能創(chuàng)建一個(gè)機(jī)器人,可惜在國(guó)內(nèi)無(wú)法使用。所以本文就來(lái)開發(fā)一個(gè)釘釘通知機(jī)器人吧
    2023-02-02
  • Python采集天天基金數(shù)據(jù)掌握最新基金動(dòng)向

    Python采集天天基金數(shù)據(jù)掌握最新基金動(dòng)向

    這篇文章主要介紹了Python采集天天基金數(shù)據(jù)掌握最新基金動(dòng)向,本次案例實(shí)現(xiàn)流程為發(fā)送請(qǐng)求、獲取數(shù)據(jù)、解析數(shù)據(jù)、多頁(yè)爬取、保存數(shù)據(jù),接下來(lái)來(lái)看看具體的操作過(guò)程吧
    2022-01-01
  • 公眾號(hào)接入chatGPT的詳細(xì)教程 附Python源碼

    公眾號(hào)接入chatGPT的詳細(xì)教程 附Python源碼

    這篇文章主要介紹了公眾號(hào)接入chatGPT教程附Python源碼,這里需要大家準(zhǔn)備一個(gè)域名,一臺(tái)服務(wù)器和一個(gè)公眾號(hào),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • django表單實(shí)現(xiàn)下拉框的示例講解

    django表單實(shí)現(xiàn)下拉框的示例講解

    今天小編就為大家分享一篇django表單實(shí)現(xiàn)下拉框的示例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05

最新評(píng)論