0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Python多線程的使用

jf_78858299 ? 來源:QStack ? 作者: 月下西樓 ? 2023-03-17 14:57 ? 次閱讀

前言

最近常常需要處理大量的crash數(shù)據(jù),對(duì)這些數(shù)據(jù)進(jìn)行分析,在此之前需要將存量的數(shù)據(jù)導(dǎo)入自己的數(shù)據(jù)庫(kù),開始一天一天的去導(dǎo),發(fā)現(xiàn)太慢了,后來嘗試通過python多線程并行導(dǎo)入多天數(shù)據(jù),以此記錄對(duì)于Python多線程的使用。

進(jìn)程與線程

在介紹Python的多線程之前,我們需要先明確一下線程和進(jìn)程的概念,其實(shí)線程和進(jìn)程是操作系統(tǒng)的基本概念,都是實(shí)現(xiàn)并發(fā)的方式,其二者的區(qū)別可以用一句話概括:進(jìn)程是資源分配的最小單位,而線程是調(diào)度的最小單位。 線程是進(jìn)程的一部分,一個(gè)進(jìn)程含有一個(gè)或多個(gè)線程。

threading的使用

Python提供了threading庫(kù)來實(shí)現(xiàn)多線程,其使用多線程的方式有兩種,一種是直接調(diào)用如下:

import threading
import time


def say(index):
    print("thread%s is running" % index)
    time.sleep(1)
    print("thread%s is over" % index)


if __name__ == "__main__":
    threading.Thread(target=say, args=(1,)).start()

?需要注意的是函數(shù)入?yún)⒌膫魅胧峭ㄟ^元組實(shí)現(xiàn)的,如果只有一個(gè)參數(shù),","是不能省略的。

?

除了以上方法,還可以通過繼承threading.Thread來實(shí)現(xiàn),代碼如下。

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, index):
        threading.Thread.__init__(self)  # 必須的步驟
        self.index = index

    def run(self):
        print("thread%s is running" % self.index)
        time.sleep(1)
        print("thread%s is over" % self.index)


if __name__ == "__main__":
    myThread = MyThread(1)
    myThread.start()

在threading中提供了很多方法,主要可以分為兩個(gè)部分,一部分是關(guān)于線程信息的函數(shù),一部分是線程對(duì)象的函數(shù)。

線程信息的函數(shù)

函數(shù) 說明
threading.active_count() 活躍線程Thread的數(shù)量
threading.current_thread() 返回當(dāng)前線程的thread對(duì)象
threading.enumerate() 返回當(dāng)前存活線程的列表
threading.main_thread() 返回當(dāng)前主線程的Thread對(duì)象

線程對(duì)象Thread的函數(shù)和屬性

函數(shù) 說明
Thread.name 線程名,可相同
Thread.ident 線程標(biāo)識(shí)符,非零整數(shù)
Thread.Daemon 是否為守護(hù)線程
Thread.is_alive() 是否存活
Thread.start() 開啟線程,多次調(diào)用會(huì)報(bào)錯(cuò)
Thread.join(timeout=None) 等待線程結(jié)束
Thread(group=None, target=None, name=None, args=(), kwargs={}, *, deamon=None) 構(gòu)造函數(shù)
Thread.run() 用來重載

線程池

線程可以提高程序的并行性,提高程序執(zhí)行的效率,雖然python的多線程只是一種假象的多線程,但是在一些io密集的程序中還是可以提高執(zhí)行效率,其中的細(xì)節(jié)會(huì)在后面詳細(xì)解釋。在多線程中線程的調(diào)度也會(huì)造成一定的開銷,線程數(shù)量越多,調(diào)度開銷越大,所以我們需要控制線程的數(shù)量,使用join可以在主線程等待子線程執(zhí)行結(jié)束,從而控制線程的數(shù)量。其代碼如下

import threading
import time


def say(index):
    print("thread%s is running" % index)
    time.sleep(1)
    print("thread%s is over" % index)


if __name__ == "__main__":
    for i in range(1, 4, 2):
        thread1 = threading.Thread(target=say, args=(i,))
        thread2 = threading.Thread(target=say, args=(i + 1,))
        thread1.start()
        thread2.start()
        thread1.join()
        thread2.join()

結(jié)果如下

thread1 is running
thread2 is running
thread1 is over
thread2 is over
thread3 is running
thread4 is running
thread3 is over
thread4 is over

如果不使用join其結(jié)果如下:

thread1 is running
thread2 is running
thread3 is running
thread4 is running
thread1 is over
thread2 is over
thread4 is over
thread3 is over

這時(shí)候是同時(shí)啟動(dòng)了四個(gè)線程

使用join來控制線程數(shù)量雖然可以達(dá)到目的,但是這樣的實(shí)現(xiàn)確實(shí)很不優(yōu)雅,而且線程的創(chuàng)建和銷毀也是很大的開銷,所以針對(duì)一些執(zhí)行頻率高且執(zhí)行時(shí)間短的情況,可以使用線程池,線程池顧名思義就是一個(gè)包含固定數(shù)量線程的池子,線程池里面的線程是可以重復(fù)利用的,執(zhí)行完任務(wù)后不會(huì)立刻銷毀而且返回線程池中等待,如果有任務(wù)則立即執(zhí)行下一個(gè)任務(wù)。

python中的concurrent.futures模塊提供了ThreadPoolExector類來創(chuàng)建線程池,其提供了以下方法:

函數(shù) 說明
submit(fn, *args, **kwargs) 將 fn 函數(shù)提交給線程池。args 代表傳給 fn 函數(shù)的參數(shù),kwargs 代表以關(guān)鍵字參數(shù)的形式為 fn 函數(shù)傳入?yún)?shù)。
map(func, *iterables, timeout=None, chunksize=1) 該函數(shù)將會(huì)啟動(dòng)多個(gè)線程,以異步方式立即對(duì) iterables 執(zhí)行 map 處理。超時(shí)拋出TimeoutError錯(cuò)誤。返回每個(gè)函數(shù)的結(jié)果,注意不是返回future。
shutdown(wait=True) 關(guān)閉線程池。關(guān)閉之后線程池不再接受新任務(wù),但會(huì)將之前提交的任務(wù)完成。

有一些函數(shù)的執(zhí)行是有返回值的,將任務(wù)通過submit提交給線程池后,會(huì)返回一個(gè)Future對(duì)象,F(xiàn)uture有以下幾個(gè)方法:

函數(shù) 說明
cancel() 取消該 Future 代表的線程任務(wù)。如果該任務(wù)正在執(zhí)行,不可取消,則該方法返回 False;否則,程序會(huì)取消該任務(wù),并返回 True。
cancelled() 如果該 Future 代表的線程任務(wù)正在執(zhí)行、不可被取消,該方法返回 True。
running() 如果該 Future 代表的線程任務(wù)正在執(zhí)行、不可被取消,該方法返回 True。
done() 如果該 Funture 代表的線程任務(wù)被成功取消或執(zhí)行完成,則該方法返回 True。
result(timeout=None) 獲取該 Future 代表的線程任務(wù)最后返回的結(jié)果。如果 Future 代表的線程任務(wù)還未完成,該方法將會(huì)阻塞當(dāng)前線程,其中 timeout 參數(shù)指定最多阻塞多少秒。超時(shí)拋出TimeoutError,取消拋出CancelledError。
exception(timeout=None) 獲取該 Future 代表的線程任務(wù)所引發(fā)的異常。如果該任務(wù)成功完成,沒有異常,則該方法返回 None。
add_done_callback(fn) 為該 Future 代表的線程任務(wù)注冊(cè)一個(gè)“回調(diào)函數(shù)”,當(dāng)該任務(wù)成功完成時(shí),程序會(huì)自動(dòng)觸發(fā)該 fn 函數(shù),參數(shù)是future。

之前的問題可以用線程池,代碼如下

import time
from concurrent.futures import ThreadPoolExecutor


def say(index):
    print("thread%s is running" % index)
    time.sleep(1)
    print("thread%s is over" % index)


if __name__ == "__main__":
    params = tuple()
    for i in range(1, 11):
        params = params + (i,)
    pool = ThreadPoolExecutor(max_workers=2)
    pool.map(say, params)

線程安全與鎖

正如之前所提到的,線程之間是共享資源的,所以當(dāng)多個(gè)線程同時(shí)訪問或處理同一資源時(shí)會(huì)產(chǎn)生一定的問題,會(huì)造成資源損壞或者與預(yù)期不一致。例如以下程序最后執(zhí)行結(jié)果是157296且每次結(jié)果都不一樣。

import threading
import time

lock = threading.Lock()


def task():
    global a
    for i in range(100000):
        a = a + 1
        if i == 50:
            time.sleep(1)


if __name__ == "__main__":
    global a
    a = 0
    thread1 = threading.Thread(target=task)
    thread2 = threading.Thread(target=task)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(a)

這時(shí)候就需要用到鎖,是使用之前將資源鎖定,鎖定期間不允許其他線程訪問,使用完之后再釋放鎖。在python的threading模塊中有Lock和RLock兩個(gè)類,它們都有兩個(gè)方法,Lock.acquire(blocking=True, timeout=-1) 獲取鎖。Lock.release() 釋放鎖。其二者的區(qū)別在于RLock是可重入鎖,一個(gè)線程可以多次獲取,主要是為了避免死鎖。一個(gè)簡(jiǎn)單的例子,以下代碼會(huì)死鎖

Lock.acquire()
Lock.acquire()
Lock.release()
Lock.release()

用RLock則不會(huì)死鎖

RLock.acquire()
RLock.acquire()
RLock.release()
RLock.release()

?死鎖(Deadlock)是指兩個(gè)或兩個(gè)以上的線程在執(zhí)行過程中,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。

?

以上代碼加鎖后就可以得到想要的結(jié)果了,其代碼如下

import threading
import time

lock = threading.Lock()


def task():
    global a
    for i in range(100000):
        lock.acquire()
        a = a + 1
        lock.release()
        if i == 50:
            time.sleep(1)


if __name__ == "__main__":
    global a
    a = 0
    thread1 = threading.Thread(target=task)
    thread2 = threading.Thread(target=task)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(a)

假的多線程

關(guān)于python多線程的簡(jiǎn)單使用已經(jīng)講完了,現(xiàn)在回到之前文中提到的,python的多線程是假的多線程,為什么這么說呢,因?yàn)镻ython中有一個(gè)GIL,GIL的全稱是Global Interpreter Lock(全局解釋器鎖),并且由于GIL鎖存在,python里一個(gè)進(jìn)程永遠(yuǎn)只能同時(shí)執(zhí)行一個(gè)線程(拿到GIL的線程才能執(zhí)行),這就是為什么在多核CPU上,python的多線程效率并不高。對(duì)于計(jì)算密集型的Python多線程并不會(huì)提高執(zhí)行效率,甚至可能因?yàn)榫€程切換開銷過大導(dǎo)致性能還不如單線程。但是對(duì)于IO密集型的任務(wù),Python多線程還是可以提高效率。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    7035

    瀏覽量

    89045
  • 多線程
    +關(guān)注

    關(guān)注

    0

    文章

    278

    瀏覽量

    19963
  • python
    +關(guān)注

    關(guān)注

    56

    文章

    4797

    瀏覽量

    84694
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Python多線程編程運(yùn)行【python簡(jiǎn)單入門】

    Python多線程類似于同時(shí)執(zhí)行多個(gè)不同程序,但其執(zhí)行過程中和進(jìn)程還是有區(qū)別的,每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口,但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序
    發(fā)表于 02-01 18:48

    Python多線程編程原理

    Python多線程類似于同時(shí)執(zhí)行多個(gè)不同程序,但其執(zhí)行過程中和進(jìn)程還是有區(qū)別的,每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口,但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序
    發(fā)表于 11-22 14:01

    淺析Python使用多線程實(shí)現(xiàn)串口收發(fā)數(shù)據(jù)

    Python使用多線程實(shí)現(xiàn)串口收發(fā)數(shù)據(jù)前言代碼最后前言近期要寫個(gè)串口的代碼,想著工程有點(diǎn)大打算用多線程布局…在使用這份代碼之前,
    發(fā)表于 08-24 07:49

    python多線程和多進(jìn)程對(duì)比

    段可以干多件事,譬如可以邊吃飯邊看電視;在Python中,多線程 和 協(xié)程 雖然是嚴(yán)格上來說是串行,但卻比一般的串行程序執(zhí)行效率高得很。 一般的串行程序,在程序阻塞的時(shí)候,只能干等著,不能去做其他事
    發(fā)表于 03-15 16:42

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

    Python的設(shè)計(jì)哲學(xué)是“優(yōu)雅”、“明確”、“簡(jiǎn)單”。因此,Perl語(yǔ)言中“總是有多種方法來做同一件事”的理念在Python開發(fā)者中通常是難以忍受的。Python開發(fā)者的哲學(xué)是“用一種方法,最好是只有一種方法來做一件事”。在設(shè)計(jì)
    發(fā)表于 12-01 09:04 ?6201次閱讀
    <b class='flag-5'>python</b><b class='flag-5'>多線程</b>與多進(jìn)程的區(qū)別

    AI算法工程師面試題匯總

    Python給自己打多少分?Python多線程怎么實(shí)現(xiàn)?
    的頭像 發(fā)表于 04-25 09:27 ?5602次閱讀

    python多線程和多進(jìn)程的對(duì)比

    在同一時(shí)間段可以干多件事,譬如可以邊吃飯邊看電視; 在Python中, 多線程 和 協(xié)程 雖然是嚴(yán)格上來說是串行,但卻比一般的串行程序執(zhí)行效率高得很。 一般的串行程序,在程序阻塞的時(shí)候,只能干等著,不能去做其他事。就好像,電視上播完正
    的頭像 發(fā)表于 03-15 16:42 ?1998次閱讀
    <b class='flag-5'>python</b><b class='flag-5'>多線程</b>和多進(jìn)程的對(duì)比

    使用map函數(shù)實(shí)現(xiàn)Python程序并行化

    Python 在程序并行化方面多少有些聲名狼藉。撇開技術(shù)上的問題,例如線程的實(shí)現(xiàn)和 GIL,我覺得錯(cuò)誤的教學(xué)指導(dǎo)才是主要問題。常見的經(jīng)典 Python 多線程、多進(jìn)程教程多顯得偏"重"
    的頭像 發(fā)表于 06-12 16:31 ?1690次閱讀

    Python-多線程、多進(jìn)程、協(xié)程

    幾乎所有的操作系統(tǒng)都支持同時(shí)運(yùn)行多個(gè)任務(wù),一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程
    的頭像 發(fā)表于 02-16 15:46 ?691次閱讀
    <b class='flag-5'>Python</b>-<b class='flag-5'>多線程</b>、多進(jìn)程、協(xié)程

    一行Python代碼實(shí)現(xiàn)并行

    Python 在程序并行化方面多少有些聲名狼藉。撇開技術(shù)上的問題,例如線程的實(shí)現(xiàn)和 GIL,我覺得錯(cuò)誤的教學(xué)指導(dǎo)才是主要問題。常見的經(jīng)典 Python 多線程、多進(jìn)程教程多顯得偏"重"
    的頭像 發(fā)表于 04-06 11:00 ?577次閱讀

    Python多進(jìn)程學(xué)習(xí)

    Python 多進(jìn)程 (Multiprocessing) 是一種同時(shí)利用計(jì)算機(jī)多個(gè)處理器核心 (CPU cores) 進(jìn)行并行處理的技術(shù),它與 Python多線程 (Multithreading
    的頭像 發(fā)表于 04-26 11:04 ?551次閱讀

    網(wǎng)絡(luò)工程師學(xué)Python-多線程技術(shù)簡(jiǎn)述

    Python多線程是一種并發(fā)編程的方式,通過使用多個(gè)線程在同一時(shí)間內(nèi)執(zhí)行多個(gè)任務(wù)
    的頭像 發(fā)表于 04-28 09:49 ?559次閱讀
    網(wǎng)絡(luò)工程師學(xué)<b class='flag-5'>Python</b>-<b class='flag-5'>多線程</b>技術(shù)簡(jiǎn)述

    一文掌握Python多線程

    使用線程可以把占據(jù)長(zhǎng)時(shí)間的程序中的任務(wù)放到后臺(tái)去處理。
    的頭像 發(fā)表于 08-05 15:46 ?859次閱讀