日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Python多線程是什么及怎么用

本篇內(nèi)容介紹了“Python多線程是什么及怎么用”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括覃塘網(wǎng)站建設(shè)、覃塘網(wǎng)站制作、覃塘網(wǎng)頁(yè)制作以及覃塘網(wǎng)絡(luò)營(yíng)銷策劃等。多年來(lái),我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,覃塘網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到覃塘省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

什么是線程?你為什么想要它?

從本質(zhì)上講,Python是一種線性語(yǔ)言,但當(dāng)你需要更多的處理能力時(shí),線程模塊非常方便。雖然Python中的線程不能用于并行CPU計(jì)算,但它非常適合I/O操作,如web抓取,因?yàn)樘幚砥魈幱诳臻e狀態(tài),等待數(shù)據(jù)。

線程正在改變游戲規(guī)則,因?yàn)樵S多與網(wǎng)絡(luò)/數(shù)據(jù)I/O相關(guān)的腳本將大部分時(shí)間用于等待來(lái)自遠(yuǎn)程源的數(shù)據(jù)。由于下載可能沒(méi)有鏈接(即,抓取單獨(dú)的網(wǎng)站),處理器可以并行地從不同的數(shù)據(jù)源下載,并在最后合并結(jié)果。對(duì)于CPU密集型進(jìn)程,使用線程模塊沒(méi)有什么好處。

Python多線程是什么及怎么用

幸運(yùn)的是,線程包含在標(biāo)準(zhǔn)庫(kù)中:

import threading
from queue import Queue
import time

你可以使用target作為可調(diào)用對(duì)象,使用args將參數(shù)傳遞給函數(shù),并start啟動(dòng)線程。

def testThread(num):
    print num

if __name__ == '__main__':
    for i in range(5):
        t = threading.Thread(target=testThread, arg=(i,))
        t.start()

如果你以前從未見(jiàn)過(guò)if __name__ == '__main__':,這基本上是一種確保嵌套在其中的代碼僅在腳本直接運(yùn)行(而不是導(dǎo)入)時(shí)運(yùn)行的方法。

同一操作系統(tǒng)進(jìn)程的線程將計(jì)算工作負(fù)載分布到多個(gè)內(nèi)核中,如C++和Java等編程語(yǔ)言所示。通常,python只使用一個(gè)進(jìn)程,從該進(jìn)程生成一個(gè)主線程來(lái)執(zhí)行運(yùn)行時(shí)。由于一種稱為全局解釋器鎖(global interpreter lock)的鎖定機(jī)制,它保持在單個(gè)核上,而不管計(jì)算機(jī)有多少核,也不管產(chǎn)生了多少新線程,這種機(jī)制是為了防止所謂的競(jìng)爭(zhēng)條件。

Python多線程是什么及怎么用

提到競(jìng)爭(zhēng),我想到了想到 NASCAR 和一級(jí)方程式賽車。讓我們用這個(gè)類比,想象所有一級(jí)方程式賽車手都試圖同時(shí)在一輛賽車上比賽。聽(tīng)起來(lái)很荒謬,對(duì)吧?,這只有在每個(gè)司機(jī)都可以使用自己的車的情況下才有可能,或者最好還是一次跑一圈,每次把車交給下一個(gè)司機(jī)。

這與線程中發(fā)生的情況非常相似。線程是從“主”線程“派生”的,每個(gè)后續(xù)線程都是前一個(gè)線程的副本。這些線程都存在于同一進(jìn)程“上下文”(事件或競(jìng)爭(zhēng))中,因此分配給該進(jìn)程的所有資源(如內(nèi)存)都是共享的。例如,在典型的python解釋器會(huì)話中:

>>> a = 8

在這里,a 通過(guò)讓內(nèi)存中的某個(gè)任意位置暫時(shí)保持值 8 來(lái)消耗很少的內(nèi)存 (RAM)。

到目前為止一切順利,讓我們啟動(dòng)一些線程并觀察它們的行為,當(dāng)添加兩個(gè)數(shù)字x時(shí)y

import time
import threading
from threading import Thread

a = 8

def threaded_add(x, y):
    # simulation of a more complex task by asking
    # python to sleep, since adding happens so quick!
    for i in range(2):
        global a
        print("computing task in a different thread!")
        time.sleep(1)
        #this is not okay! but python will force sync, more on that later!
        a = 10
        print(a)

# the current thread will be a subset fork!
if __name__ != "__main__":
    current_thread = threading.current_thread()


# here we tell python from the main 
# thread of execution make others
if __name__ == "__main__":

    thread = Thread(target = threaded_add, args = (1, 2))
    thread.start()
    thread.join()
    print(a)
    print("main thread finished...exiting")
>>> computing task in a different thread!
>>> 10
>>> computing task in a different thread!
>>> 10
>>> 10
>>> main thread finished...exiting

兩個(gè)線程當(dāng)前正在運(yùn)行。讓我們把它們稱為thread_onethread_two。如果thread_one想要用值10修改a,而thread_two同時(shí)嘗試更新同一變量,我們就有問(wèn)題了!將出現(xiàn)稱為數(shù)據(jù)競(jìng)爭(zhēng)的情況,并且a的結(jié)果值將不一致。

一場(chǎng)你沒(méi)有看的賽車比賽,但從你的兩個(gè)朋友那里聽(tīng)到了兩個(gè)相互矛盾的結(jié)果!thread_one告訴你一件事,thread two反駁了這一點(diǎn)!這里有一個(gè)偽代碼片段說(shuō)明:

a = 8
# spawns two different threads 1 and 2
# thread_one updates the value of a to 10

if (a == 10):
  # a check

#thread_two updates the value of a to 15
a = 15
b = a * 2

# if thread_one finished first the result will be 20
# if thread_two finished first the result will be 30
# who is right?

到底是怎么回事?

Python是一種解釋語(yǔ)言,這意味著它帶有一個(gè)解釋器——一個(gè)從另一種語(yǔ)言解析其源代碼的程序!python中的一些此類解釋器包括cpython、pypypy、Jpython和IronPython,其中,cpython是python的原始實(shí)現(xiàn)。

CPython是一個(gè)解釋器,它提供與C以及其他編程語(yǔ)言的外部函數(shù)接口,它將python源代碼編譯成中間字節(jié)碼,由CPython虛擬機(jī)進(jìn)行解釋。迄今為止和未來(lái)的討論都是關(guān)于CPython和理解環(huán)境中的行為。

內(nèi)存模型和鎖定機(jī)制

編程語(yǔ)言使用程序中的對(duì)象來(lái)執(zhí)行操作。這些對(duì)象由基本數(shù)據(jù)類型組成,如stringintegerboolean。它們還包括更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),如listclasses/objects。程序?qū)ο蟮闹荡鎯?chǔ)在內(nèi)存中,以便快速訪問(wèn)。在程序中使用變量時(shí),進(jìn)程將從內(nèi)存中讀取值并對(duì)其進(jìn)行操作。在早期的編程語(yǔ)言中,大多數(shù)開(kāi)發(fā)人員負(fù)責(zé)他們程序中的所有內(nèi)存管理。這意味著在創(chuàng)建列表或?qū)ο笾?,首先必須為變量分配?nèi)存。在這樣做時(shí),你可以繼續(xù)釋放以“釋放”內(nèi)存。

在python中,對(duì)象通過(guò)引用存儲(chǔ)在內(nèi)存中。引用是對(duì)象的一種標(biāo)簽,因此一個(gè)對(duì)象可以有許多名稱,比如你如何擁有給定的名稱和昵稱。引用是對(duì)象的精確內(nèi)存位置。引用計(jì)數(shù)器用于python中的垃圾收集,這是一種自動(dòng)內(nèi)存管理過(guò)程。

在引用計(jì)數(shù)器的幫助下,python通過(guò)在創(chuàng)建或引用對(duì)象時(shí)遞增引用計(jì)數(shù)器和在取消引用對(duì)象時(shí)遞減來(lái)跟蹤每個(gè)對(duì)象。當(dāng)引用計(jì)數(shù)為0時(shí),對(duì)象的內(nèi)存將被釋放。

import sys
import gc

hello = "world" #reference to 'world' is 2
print (sys.getrefcount(hello))

bye = "world" 
other_bye = bye 
print(sys.getrefcount(bye)) 
print(gc.get_referrers(other_bye))
>>> 4
>>> 6
>>> [['sys', 'gc', 'hello', 'world', 'print', 'sys', 'getrefcount', 'hello', 'bye', 'world', 'other_bye', 'bye', 'print', 'sys', 'getrefcount', 'bye', 'print', 'gc', 'get_referrers', 'other_bye'], (0, None, 'world'), {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0138ADF0>, '__spec__': None, '__annotations__': {}, '__builtins__': , '__file__': 'test.py', '__cached__': None, 'sys': , 'gc': , 'hello': 'world', 'bye': 'world', 'other_bye': 'world'}]

需要保護(hù)這些參考計(jì)數(shù)器變量,防止競(jìng)爭(zhēng)條件或內(nèi)存泄漏。以保護(hù)這些變量;可以將鎖添加到跨線程共享的所有數(shù)據(jù)結(jié)構(gòu)中。

CPython 的 GIL 通過(guò)一次允許一個(gè)線程控制解釋器來(lái)控制 Python 解釋器。它為單線程程序提供了性能提升,因?yàn)橹恍枰芾硪粋€(gè)鎖,但代價(jià)是它阻止了多線程 CPython 程序在某些情況下充分利用多處理器系統(tǒng)。

當(dāng)用戶編寫python程序時(shí),性能受CPU限制的程序和受I/O限制的程序之間存在差異。CPU通過(guò)同時(shí)執(zhí)行許多操作將程序推到極限,而I/O程序必須花費(fèi)時(shí)間等待I/O。

因此,只有多線程程序在GIL中花費(fèi)大量時(shí)間來(lái)解釋CPython字節(jié)碼;GIL成為瓶頸。即使沒(méi)有嚴(yán)格必要,GIL也會(huì)降低性能。例如,一個(gè)用python編寫的同時(shí)處理IO和CPU任務(wù)的程序:

import time, os
from threading import Thread, current_thread
from multiprocessing import current_process

COUNT = 200000000
SLEEP = 10

def io_bound(sec):
   pid = os.getpid()
   threadName = current_thread().name
   processName = current_process().name
   print(f"{pid} * {processName} * {threadName} \
           ---> Start sleeping...")
   time.sleep(sec)
   print(f"{pid} * {processName} * {threadName} \
           ---> Finished sleeping...")

def cpu_bound(n):
   pid = os.getpid()
   threadName = current_thread().name
   processName = current_process().name
   print(f"{pid} * {processName} * {threadName} \
           ---> Start counting...")
   while n>0:
          n -= 1
   print(f"{pid} * {processName} * {threadName} \
       ---> Finished counting...")

 def timeit(function,args,threaded=False):
      start = time.time()
      if threaded:
         t1 = Thread(target = function, args =(args, ))
         t2 = Thread(target = function, args =(args, ))
         t1.start()
         t2.start()
         t1.join()
         t2.join()
      else:
        function(args)
      end = time.time()
      print('Time taken in seconds for running {} on Argument {} is {}s -{}'.format(function,args,end - start,"Threaded" if threaded else "None Threaded"))

if __name__=="__main__":
      #Running io_bound task
      print("IO BOUND TASK NON THREADED")
      timeit(io_bound,SLEEP)

      print("IO BOUND TASK THREADED")
      #Running io_bound task in Thread
      timeit(io_bound,SLEEP,threaded=True)

      print("CPU BOUND TASK NON THREADED")
      #Running cpu_bound task
      timeit(cpu_bound,COUNT)

      print("CPU BOUND TASK THREADED")
      #Running cpu_bound task in Thread
      timeit(cpu_bound,COUNT,threaded=True)
>>> IO BOUND TASK  NON THREADED
>>> 17244 * MainProcess * MainThread            ---> Start sleeping...
>>> 17244 * MainProcess * MainThread            ---> Finished sleeping...
>>> 17244 * MainProcess * MainThread            ---> Start sleeping...
>>> 17244 * MainProcess * MainThread            ---> Finished sleeping...
>>> Time taken in seconds for running  on Argument 10 is 20.036664724349976s -None Threaded
>>> IO BOUND TASK THREADED
>>> 10180 * MainProcess * Thread-1            ---> Start sleeping...
>>> 10180 * MainProcess * Thread-2            ---> Start sleeping...
>>> 10180 * MainProcess * Thread-1            ---> Finished sleeping...
>>> 10180 * MainProcess * Thread-2            ---> Finished sleeping...
>>> Time taken in seconds for running  on Argument 10 is 10.01464056968689s -Threaded
>>> CPU BOUND TASK NON THREADED
>>> 14172 * MainProcess * MainThread            ---> Start counting...
>>> 14172 * MainProcess * MainThread        ---> Finished counting...
>>> 14172 * MainProcess * MainThread            ---> Start counting...
>>> 14172 * MainProcess * MainThread        ---> Finished counting...
>>> Time taken in seconds for running  on Argument 200000000 is 44.90199875831604s -None Threaded
>>> CPU BOUND TASK THEADED
>>> 15616 * MainProcess * Thread-1            ---> Start counting...
>>> 15616 * MainProcess * Thread-2            ---> Start counting...
>>> 15616 * MainProcess * Thread-1        ---> Finished counting...
>>> 15616 * MainProcess * Thread-2        ---> Finished counting...
>>> Time taken in seconds for running  on Argument 200000000 is 106.09711360931396s -Threaded

從結(jié)果中我們注意到,multithreading在多個(gè)IO綁定任務(wù)中表現(xiàn)出色,執(zhí)行時(shí)間為10秒,而非線程方法執(zhí)行時(shí)間為20秒。我們使用相同的方法執(zhí)行CPU密集型任務(wù)。好吧,最初它確實(shí)同時(shí)啟動(dòng)了我們的線程,但最后,我們看到整個(gè)程序的執(zhí)行需要大約106秒!然后發(fā)生了什么?這是因?yàn)楫?dāng)Thread-1啟動(dòng)時(shí),它獲取全局解釋器鎖(GIL),這防止Thread-2使用CPU。因此,Thread-2必須等待Thread-1完成其任務(wù)并釋放鎖,以便它可以獲取鎖并執(zhí)行其任務(wù)。鎖的獲取和釋放增加了總執(zhí)行時(shí)間的開(kāi)銷。因此,可以肯定地說(shuō),線程不是依賴CPU執(zhí)行任務(wù)的理想解決方案。

這種特性使并發(fā)編程變得困難。如果GIL在并發(fā)性方面阻礙了我們,我們是不是應(yīng)該擺脫它,還是能夠關(guān)閉它?。嗯,這并不容易。其他功能、庫(kù)和包都依賴于GIL,因此必須有一些東西來(lái)取代它,否則整個(gè)生態(tài)系統(tǒng)將崩潰。這是一個(gè)很難解決的問(wèn)題。

多進(jìn)程

我們已經(jīng)證實(shí),CPython使用鎖來(lái)保護(hù)數(shù)據(jù)不受競(jìng)速的影響,盡管這種鎖存在,但程序員已經(jīng)找到了一種顯式實(shí)現(xiàn)并發(fā)的方法。當(dāng)涉及到GIL時(shí),我們可以使用multiprocessing庫(kù)來(lái)繞過(guò)全局鎖。多處理實(shí)現(xiàn)了真正意義上的并發(fā),因?yàn)樗诓煌珻PU核上跨不同進(jìn)程執(zhí)行代碼。它創(chuàng)建了一個(gè)新的Python解釋器實(shí)例,在每個(gè)內(nèi)核上運(yùn)行。不同的進(jìn)程位于不同的內(nèi)存位置,因此它們之間的對(duì)象共享并不容易。在這個(gè)實(shí)現(xiàn)中,python為每個(gè)要運(yùn)行的進(jìn)程提供了不同的解釋器;因此在這種情況下,為多處理中的每個(gè)進(jìn)程提供單個(gè)線程。

import os
import time
from multiprocessing import Process, current_process

SLEEP = 10
COUNT = 200000000

def count_down(cnt):
   pid = os.getpid()
   processName = current_process().name
   print(f"{pid} * {processName} \
           ---> Start counting...")
   while cnt > 0:
       cnt -= 1

def io_bound(sec):
   pid = os.getpid()
   threadName = current_thread().name
   processName = current_process().name
   print(f"{pid} * {processName} * {threadName} \
           ---> Start sleeping...")
   time.sleep(sec)
   print(f"{pid} * {processName} * {threadName} \
           ---> Finished sleeping...")

if __name__ == '__main__':
# creating processes
    start = time.time()

    #CPU BOUND
    p1 = Process(target=count_down, args=(COUNT, ))
    p2 = Process(target=count_down, args=(COUNT, ))

    #IO BOUND
    #p1 = Process(target=, args=(SLEEP, ))
    #p2 = Process(target=count_down, args=(SLEEP, ))

  # starting process_thread
    p1.start()
    p2.start()

  # wait until finished
    p1.join()
    p2.join()

    stop = time.time()
    elapsed = stop - start

    print ("The time taken in seconds is :", elapsed)
>>> 1660 * Process-2            ---> Start counting...
>>> 10184 * Process-1            ---> Start counting...
>>> The time taken in seconds is : 12.815475225448608

可以看出,對(duì)于cpu和io綁定任務(wù),multiprocessing性能異常出色。MainProcess啟動(dòng)了兩個(gè)子進(jìn)程,Process-1Process-2,它們具有不同的PIDs,每個(gè)都執(zhí)行將COUNT減少到零的任務(wù)。每個(gè)進(jìn)程并行運(yùn)行,使用單獨(dú)的CPU內(nèi)核和自己的Python解釋器實(shí)例,因此整個(gè)程序執(zhí)行只需12秒。

請(qǐng)注意,輸出可能以無(wú)序的方式打印,因?yàn)檫^(guò)程彼此獨(dú)立。這是因?yàn)槊總€(gè)進(jìn)程都在自己的默認(rèn)主線程中執(zhí)行函數(shù)。

我們還可以使用asyncio庫(kù)(上一節(jié)我已經(jīng)講過(guò)了,沒(méi)看的可以返回到上一節(jié)去學(xué)習(xí))繞過(guò)GIL鎖。asyncio的基本概念是,一個(gè)稱為事件循環(huán)的python對(duì)象控制每個(gè)任務(wù)的運(yùn)行方式和時(shí)間。事件循環(huán)知道每個(gè)任務(wù)及其狀態(tài)。就緒狀態(tài)表示任務(wù)已準(zhǔn)備好運(yùn)行,等待階段表示任務(wù)正在等待某個(gè)外部任務(wù)完成。在異步IO中,任務(wù)永遠(yuǎn)不會(huì)放棄控制,也不會(huì)在執(zhí)行過(guò)程中被中斷,因此對(duì)象共享是線程安全的。

import time
import asyncio

COUNT = 200000000

# asynchronous function defination
async def func_name(cnt):
       while cnt > 0:
           cnt -= 1

#asynchronous main function defination
async def main ():
  # Creating 2 tasks.....You could create as many tasks (n tasks)
  task1 = loop.create_task(func_name(COUNT))
  task2 = loop.create_task(func_name(COUNT))

  # await each task to execute before handing control back to the program
  await asyncio.wait([task1, task2])

if __name__ =='__main__':
  # get the event loop
  start_time = time.time()
  loop = asyncio.get_event_loop()
  # run all tasks in the event loop until completion
  loop.run_until_complete(main())
  loop.close()
  print("--- %s seconds ---" % (time.time() - start_time))
>>> --- 41.74118399620056 seconds ---

我們可以看到,asyncio需要41秒來(lái)完成倒計(jì)時(shí),這比multithreading的106秒要好,但對(duì)于cpu受限的任務(wù),不如multiprocessing的12秒。Asyncio創(chuàng)建一個(gè)eventloop和兩個(gè)任務(wù)task1task2,然后將這些任務(wù)放在eventloop上。然后,程序await任務(wù)的執(zhí)行,因?yàn)槭录h(huán)執(zhí)行所有任務(wù)直至完成。

為了充分利用python中并發(fā)的全部功能,我們還可以使用不同的解釋器。JPython和IronPython沒(méi)有GIL,這意味著用戶可以充分利用多處理器系統(tǒng)。

與線程一樣,多進(jìn)程仍然存在缺點(diǎn):

  1. 數(shù)據(jù)在進(jìn)程之間混洗會(huì)產(chǎn)生 I/O 開(kāi)銷

  2. 整個(gè)內(nèi)存被復(fù)制到每個(gè)子進(jìn)程中,這對(duì)于更重要的程序來(lái)說(shuō)可能是很多開(kāi)銷

“Python多線程是什么及怎么用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!


網(wǎng)頁(yè)名稱:Python多線程是什么及怎么用
瀏覽地址:http://www.dlmjj.cn/article/jsedsg.html