Python 多線程入門,這一篇文章就夠了

2021-02-15 python爬蟲人工智慧大數據
Python 和多線程

提及 Python 啊,我想你首先想到的就是「人生苦短,我用 Python」了。現在 Python 的熱度可謂是非常的高,感覺程式設計師要是不學 Python 的話,就有一種 out 了的感覺,雖然現在工業界使用 Python 的人數遠沒有 Java 的人多, 但 Python 是未來的趨勢是非常明顯的,因此呢,學習 Python 自然就是一件很有必要的事情了,今天呢,我就帶你一起聊聊 Python 多線程相關的那些事。

關於多線程啊,我想你肯定不陌生,無論是高級語言的鼻祖 C 語言、還是 C++、Java,都支持多線程、多進程,而且這部分知識無論是在求職面試還是在日常的工作開發中,都會涉及到,不巧的是呢,這部分知識在老師講課過程中是很少涉及的,甚至是直接不講,我記得我當時老師就沒有講,這不是說老師不合格,偷懶了,而是一門語言涉及到的知識太多了,老師只能把一些基礎的東西交給你,帶你入門,剩下的就需要自己去摸索、自學了。

線程與進程

既然提到多線程,多進程了,那就有必要先了解下線程和進程的相關概念了。要不然的話後面的內容理解起來也是有點費勁的。

提到進程啊,我想你肯定是不陌生的,我們在電腦上打開一個軟體,就是開啟了一個進程,更具體的來說,Windows 系統你可以通過資源管理器進行查看當前電腦啟動的進程數。

用比較正式的話來說,進程就是處於運行中的程序,並且具有一定獨立的功能。進程是作業系統進行資源分配和調度的一個獨立單位。

然後就是線程,它是進程的組成部分,一個線程可以包含多個線程,多個線程可以共用這個進程的資源,相比於進程,線程更加輕量級。

舉個例子來說明下:我們的生活都是以家單位的,每家每戶每天都有自己的計劃安排、互不影響,這時候,每家就相當於一個進程,但是呢,需要受到國家的管制,比如說,買房限購、戶口問題啊等等需要國家統一出臺政策進行管理,這時候國家就相當於作業系統,而房子、戶口就相當於資源。但是對於每一家來說,又有不同的人,這時候,每個人就相當於一個線程,多個線程之間共用家裡的一些資源,就是家裡的人共用家裡的一些東西。雖然例子不是很恰當,但對於理解線程和進程還是有很大幫助的。

線程的幾種狀態

線程狀態一共有五種,包括如下:

它們之間的關係如下圖所示:

實現方式

接下來,我們就來看看如何在 Python 裡面實現多線程。總的來說,如果你了解過其他語言實現多線程的方式,比如說 Java的話,那對於理解 Python 實現多線程是非常有幫助的。Python 實現多線程有兩種方式:

使用 threading 模塊的 Thread 類的構造器創建線程

繼承 threading 模塊的 Thread 類創造線程類

看到這,你是不是發現這和 Java 實現多線程的方式很相類,不錯,確實就是這樣,所以再次印證了那句話,只要學好了一門語言,學習其他語言都會起到事半功倍的效果。

使用 threading 模塊的 Thread 類的構造器創建線程

我們先用第一種方法來編寫一個多線程程序

#!/usr/bin/python
# -*- coding: utf-8 -*-
import threading


# 定義一個簡單的方法,用於多線程的執行體
def action(number):
  for i in range(number):
    # 調用 threading 模塊的 current_thread() 函數來獲取當前線程
    # 調用當前線程的 getName() 函數來獲取線程名
    print("{},{}".format(threading.current_thread().getName(), i))

number = 5
for i in range(5):
  print("{},{}".format(threading.current_thread().getName(), i))
  if i == 3:
    # 創建並啟動第一個線程
    t1 = threading.Thread(target=action, args=(number, ))
    t1.start()
    # 創建並啟動第二個線程
    t2 = threading.Thread(target=action, args=(number, ))
    t2.start()

看起來是不是很簡單,很我們平常寫的 Python 程序並沒有特別大的不同,但是還是有很一些情況是需要注意的,其中最重要的就是 threading.Thread(),我在這裡重點介紹下。

首先它是一個類,我們可以通過 type(threading.Thread) 來進行查看,它的構造函數如下所示:

__init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

group 應該為None,這個我們不用管,它是為了日後擴展 ThreadGroup 類實現而保留的一個參數

target 是我們需要重視的一個參數, 我們想讓哪個函數並發執行,這個函數就是 target 的參數值,注意只寫函數名,不需要寫 ()

name 是線程名稱,默認情況下,由"Thread-N"的格式構成一個唯一的名稱,其中 N 是小的十進位數

args 是用於調用目標函數的參數元祖, 注意是元祖, 如果你只想傳一個參數的話,也應該這樣寫 (args1,), 而不是 (args)

kwargs 是用於調用目標函數的關鍵字參數字典。默認是 {}

daemon 用於設置該線程是否為守護模式,如果是 None, 線程默認將繼承當前線程的守護模式屬性。

一般來說,我們需要注意的就是 target 參數、args 參數,其他的參數用到的時候可以再查。

另一點需要我們需要注意的一點就是啟動線程的方法是 start 方法,可能你也知道線程也有 run 方法,這一塊也會在第二種方法中進行介紹,但是啟動線程的方法是 start 方法,要不然就變成了單線程程序。

繼承 threading 模塊的 Thread 類創造線程類

接下來我們來看下如何使用第二種方法實現多線程

#! /usr/bin/python
# -*- coding:utf-8 -*-
import threading
from threading import Thread

# 繼承 threading.Thread
class MyThread(Thread):
  def __init__(self, number):
    super().__init__()
    self.number = number
  # 重載 run() 方法
  def run(self):
    for i in range(self.number):
      print("{}, {}".format(threading.current_thread().getName(), i))

number = 5
for i in range(5):
  print("{}, {}".format(threading.current_thread().getName(), i))
  if i == 3:
    t1 = MyThread(number=number)
    t1.start()
    t2 = MyThread(number=number)
    t2.start()

第二種方法就是繼承 Threading.Thread 類。然後重載 run() 方法。

其實我看來的話,感覺第二種方法更適合在項目中使用,因為它更加模塊化,比較清晰。

另外還有一個方法需要注意的就是 join() 方法,它的作用就是協調主線程和子線程的,調用 join() 後,當前線程就會阻塞,或者來說,暫停運行,執行子線程,等子線程執行完成後,主線程再接著運行。

生產者、消費者模型

提到多線程,最著名的就是生產者、消費者模型了,那應該如何實現呢?

說實話,我當初最開始學習生產者、消費者模型的時候,心裡是有點犯嘀咕的,感覺涉及到線程間的通信,太好解決。但是查閱了一些資料後,發現還是可以理解的。

生產者、消費者二者不屬於競爭關係,更多的是一種捕食關係,生產者生產資源,消費者進行消費,就像聖湖中的牛吃草一樣。

不知道這時候你有沒有想到一種數據結構,那就是隊列,隊列呢是一種操作受限的線性表,它只允許在隊尾入隊,在隊頭
出隊,也就是先進先出 (FIFO) 策略。

生產者、消費者模型,不就是生產者生產元素,放到隊尾,然後消費者從隊頭消費元素嘛。

只不過有時候會出現特殊的情況

隊列空了,消費者還要消費數據

隊列滿了,生產者還要生產數據

這是我們需要重點考慮了,解決了以上兩點,這個模型也就實現了。

接下來我們就來看看 Python 如何實現吧!

#!/usr/bin/python
# -*- coding:utf-8
from threading import Thread, current_thread
import time
import random
from queue import Queue

queue = Queue(5)


class ProducerThread(Thread):
    def run(self):
        name = current_thread().getName()
        nums = range(100)
        global queue
        while True:
            num = random.choice(nums)
            queue.put(num)
            print("生產者 {} 生產了數據 {}".format(name, num))
            t = random.randint(1, 3)
            time.sleep(t)
            print("生產者 {} 睡眠了 {} 秒".format(name, t))


class ConsumerThread(Thread):
    def run(self):
        name = current_thread().getName()
        global queue
        while True:
            num = queue.get()
            queue.task_done()
            print("消費者 {} 消耗了數據 {}".format(name, num))
            t = random.randint(1, 5)
            time.sleep(t)
            print("消費者 {} 睡眠了 {} 秒".format(name, t))


p1 = ProducerThread(name="producer1")
p1.start()
c1 = ConsumerThread(name="consumer1")
c1.start()
c2 = ConsumerThread(name="consumer2")
c2.start()

看了上面的代碼,不知道你有沒有一種錯覺,你不是說要考慮上面的兩種情況,但是你並沒有考慮啊。

確實,我沒有考慮,那是因為 Queue 在設計實現的時候已經替我們考慮好了,我們直接使用就好了。

具體就是 task_done() 函數,它在隊列為空時會自動阻塞當前線程

而隊列在滿的時候再添加元素也會阻塞當前線程,這就實現了上面我們提到的那兩種情況。

接下來呢,我再給你講解一個例子,帶你看看如何使用鎖。

銀行取錢問題

從銀行取錢的基本流程大致可以分為以下幾個步驟:

用戶輸入帳戶、密碼,系統判斷當前的帳戶、密碼是否匹配。

用戶輸入取款金額

系統判斷帳戶餘額是否大於取款金額

如果餘額大於取款金額,則取款成功;如果餘額小於取款金額,則取款失敗。

乍一看,這就是日常生活中的取款操作啊,但是把它放到多線程並發的情況下,就可能會出現問題。不信的話,你可以試著寫下多線程的程序,然後再看下我的程序。

#!/usr/bin/python
# -*- coding:utf-8 -*-
import threading
import time


class Account:
    def __init__(self, account_no, balance):
        self.account_no = account_no
        self._balance = balance
        # 定義一個鎖
        self.lock = threading.RLock()

    def get_balance(self):
        return self._balance

    def draw(self, draw_amount):
        # 對 RLock 對象進行加鎖
        self.lock.acquire()
        try:
            if self._balance >= draw_amount:
                print(threading.current_thread().getName() + "取錢成功,吐出鈔票:" + str(draw_amount))
                time.sleep(0.001)
                self._balance -= draw_amount
                print("\t餘額為:" + str(self._balance))
            else:
                print(threading.current_thread().getName() + "取錢失敗,餘額不足!")
        finally:
            # 釋放鎖
            self.lock.release()


# 定義一個函數來模擬取錢操作
def draw(account, draw_count):
    account.draw(draw_count)


acct = Account("1234567", 1000)
threading.Thread(name="甲", target=draw, args=(acct, 800)).start()
threading.Thread(name="乙", target=draw, args=(acct, 800)).start()

如果你想嘗試下不加鎖的情況下是否會出現問題,你可以把我的程序進行修改,把加鎖的那部分去掉,然後嘗試運行下。

這裡呢,不是說每次運行都會出現問題,可能你運行了十次也都沒有出現問題,但是呢,這個安全隱患是確確實實存在的,不容忽視。

好了,今天的內容就先分享到這裡了,不知道你對多線程的內容理解了多少,不理解的話也沒關係,多看幾遍,然後很重要的就是自己好好寫一遍實踐一下,這樣對於理解是有很大幫助的。如果遇到問題,也可以在我的公眾號底部找到我的微信聯繫方式,聯繫我。

多線程的內容有很多,今天只是分享了一些比較基礎的內容,後面會再更新,歡迎關注我,一起加油進步。

歡迎關注我的公眾號「與你一起學算法」,如果喜歡,麻煩點一下「在看」~

相關焦點

  • Python入門基礎之socket多線程編程,TCP伺服器和客戶端通信
    在上一篇文章中,我介紹了一下python3 socket編程的基礎,包括TCP、UDP客戶端和伺服器的建立過程(連結在最下方)。不過那個只是單線程的,伺服器一次只能和一個客戶端會話,多個客戶端的話只能等待。我們平時的應用中,伺服器肯定是要並發的,所以,今天將介紹socket編程的多線程編程。
  • 入門Python, 看這些資料就夠了
    學習和關注python有五年多的時間。 收藏了一些不錯的網站和資源,和大家分享。
  • Python 實例:多線程
    這是一種理所當然的順序操作方法,但理所當然的操作並不是最有效率的操作。這種方法完全就是線性操作,從列表中取出一個元素,就用函數處理一個元素。現在運行程序試試,執行命令:time python3 threadingOrderRun.py
  • Python多線程實戰
    點擊上方「Python人工智慧編程」,馬上關注目的:(1)了解python線程執行原理(2)掌握多線程編程與線程同步(3)了解線程池的使用1 線程基本概念1.1 線程是什麼?線程是指進程內的一個執行單元,也是進程內的可調度實體.
  • 理解Python中多線程
    p=2604(點擊尾部閱讀原文前往)雖然Python中由於GIL的機制致使多線程不能利用機器多核的特性,但是多線程對於我們理解並發模型以及底層操作非常有用。線程的有兩種使用方法,一種是在函數使用,一種是放在類中使用。
  • Python爬蟲從入門到精通(3): BeautifulSoup用法總結及多線程爬蟲爬取糗事百科
    我們還會利用requests庫和BeauitfulSoup來爬取糗事百科上的段子, 並對比下單線程爬蟲和多線程爬蟲的爬取效率。什麼是BeautifulSoup及如何安裝BeautifulSoup是一個解析HTML或XML文件的第三方庫。
  • 一篇文章入門Python生態系統
    希望經過不斷的增補修訂,本文會成為Python生態系統方面的一篇詳盡教程。本文的目的,不是教大家Python程式語言。讀完這篇教程,你也不會瞬間變成一名Python高手。我假設大家已經有一定的Python基礎。如果你是初學者,那麼別再繼續讀下去了。先去看看Zed Shaw所寫的《笨辦法學Python》,這是本質量很高的免費電子書,看完之後再回頭閱讀這篇教程吧。
  • 菜鳥學Python入門教程大盤點|7個多月的心血總結
    詳見我的文章:1).都說Python時間處理很好玩還簡單,真的嗎13.Python裡的生成器如果說Python中有兩大最難理解的概念,其中之一就是生成器,這個語法比較特別,而且傳統的語言裡面也沒有這個概念,但是生成器其實是一個非常有用的東西,有的地方用生成器解決非常方便
  • Python零基礎入門教程,如何使用多線程(上)?
    大綱了解多線程創建多線程有兩種方式守護線程多線程多個任務可以用多進程完成,也可以使用同一進程中的多個線程之間可以並發執行完成創建多線程# 方式一import threadingimport timedef run(name): print('{0} 開始跑步'.format(name))
  • Python入門系列(十二)——GUI+多進程
    但在某些特殊需求下還是需要我們去使用,所以python擁有多個第三方庫用以實現GUI,本章我們使用python基本模塊tkinter進行學習,因為需求並不大,所以不做太多拓展。Unix系統提供了forx,python可藉助os模塊調用,從而實現多進程,然而windows系統並不具備,所以我們選擇python內置的multiprocessing多進程模塊進行學習。友情提示:請先自行補充線程、進程基本概念。首先我們藉助直接調用多進程來改寫下我們在多線程章節用到的例子!
  • 如何入門Python之Python基礎教程詳解
    隨著人工智慧的發展,Python近兩年也是大火,越來越多的人加入到Python學習大軍,對於毫無基礎的人該如何入門Python呢?這裡整理了一些個人經驗和Python入門教程供大家參考。如果你是零基礎入門 Python 的話,建議初學者至少達到兩個目標: 會用,理解。
  • python爬蟲入門實戰!爬取博客文章標題和連結!
    首先需要在電腦上裝好 python3 和 pip 。此外還需要知道python的一些基本語法。這些內容網上搜索有許多教程(例如廖雪峰),這邊就不再細說了。我們這次需要使用的是 正則表達式 re 庫和第三方的 requests 庫,以下是安裝方法。
  • python核心編程之多線程教程-threading
    多線程-threadingpython的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用1.time.sleep(1)if __name__ == "__main__":for i in range(5):saySorry()運行結果:多線程執行#coding=utf-8import threadingimport timedef saySorry()
  • python教程之十二多線程
    進程與線程機器分配資源(包括內存、CPU等)的最小單位是進程,任務調度採用的是時間片輪轉的搶佔式調度方式。一個進程可以有一個或多個線程,各個線程之間共享程序的內存空間(也就是所在進程的內存空間)。一個標準的線程由線程ID,當前指令指針PC,寄存器和堆棧組成,進程由內存空間和一個或多個線程組成,以下是進程與線程的區別。1.
  • 看幾段爬蟲代碼,詳解Python多線程、多進程、協程
    URL時必然會引起等待示例代碼就是典型的串行邏輯,parse_1將url和循環數傳遞給parse_2,parse_2請求並返回狀態碼後parse_1繼續迭代一次,重複之前步驟因為CPU在執行程序時每個時間刻度上只會存在一個線程,因此多線程實際上提高了進程的使用率從而提高了CPU的使用率實現多線程的庫有很多,這裡用concurrent.futures
  • 作為數據科學家你應該知道這些 python 多線程、進程知識
    python 為並行化提供了兩個內置庫:多處理和線程。在這篇文章中,我們將探討數據科學家如何在兩者之間進行選擇,以及在這樣做時應注意哪些因素。並行計算與數據科學眾所周知,數據科學是處理大量數據並從中提取有用見解的科學。通常情況下,我們對數據執行的操作很容易並行化,這意味著不同的處理代理可以一次對數據執行一個操作,最後進行組合以獲得完整的結果。
  • 入門「狼人殺」?這一篇文章就夠了!
    ,那麼,筆者就在這裡用一篇文章來教大家快速入門狼人殺。好人又可以將角色細分為神民和村民,神民即是有技能的好人,其勝利條件與好人相同只是比普通的好人多了一個特定的技能(常見的神民包括預言家、女巫、獵人、白痴、守衛等),村民即為普通村民,無任何技能,只能通過其他人的發言來找出你認為的狼人。
  • 可能是最通俗易懂的Python入門資料整理和最優學習路線推薦.
    Python 這門語言是學習數據科學和人工智慧始終繞不開的一個基礎知識和技能點,我們只有點亮這個技能點才能更好的開展我們的宏圖霸業(有網友說想用Python實現自動賺錢)。廢話不多說,我們直接進入正題。在這篇文章裡,我會把所涉及的資料分為三個部分。
  • 書聲琅琅:好的Python入門教程
    好的Python入門教程,書聲琅琅教育番茄老師微信pykf20介紹,python語言現在應用非常廣泛,不管是大數據還是人工智慧,應用最多的語言還是python,因此對於許多小白來講,看到python從業者的高薪資,想要轉行,或者致力於python開發的朋友,如果要學習python,從零基礎開始,一定需要一套完整的學習路線。
  • 給小白的禮物,菜鳥學Python入門教程大盤點
    公眾號又不支持搜索文章的功能。其實我寫了一年多,裡面有很多關於入門的文章,今天把這些連結再次總結,希望對剛入門或者迫切需要快速上手的同學有用!詳細見我的文章:"你為什麼一定要學Python?"2.Python語言如何入門認同了第一個問題的人,既然Python語言這麼好,接下來肯定想迫切學習python,那麼如何快速的上手,迅速的入門呢,我列出了3個主要的途徑,其中最後一個途徑最最最關鍵( 重要的事情說三篇).