Python 線程、線程通信、多線程

這是一篇學(xué)習(xí)Python 線程相關(guān)的內(nèi)容,記錄一下以備復(fù)習(xí)和開(kāi)發(fā)使用,技術(shù)有限,如有問(wèn)題歡迎指出,多謝。


一.GIL 全局解釋器鎖(cpython)

1.為什么會(huì)有這個(gè)鎖:為了線程安全,減少python使用者的上手難度
GIL 使得同一個(gè)時(shí)刻只有一個(gè)線程在一個(gè)cpu上執(zhí)行字節(jié)碼,無(wú)法隱射到多個(gè)cpu,多核上執(zhí)行。
2.特殊情況下會(huì)釋放GIL:達(dá)到特定字節(jié)碼行數(shù)、到底特定數(shù)目時(shí)間片、IO操作(主動(dòng))

二:并發(fā)和并行的區(qū)別
  • 并發(fā):描述程序的組織結(jié)構(gòu),指程序要被設(shè)計(jì)成多個(gè)可獨(dú)立執(zhí)行的子任務(wù)
  • 并行:描述程序的執(zhí)行狀態(tài),指多任務(wù)需要同時(shí)執(zhí)行
三:守護(hù)線程&線程阻塞
  • 守護(hù)線程:thread.setDaemon(true),當(dāng)主程序退出的時(shí)候讓子程序也一并退出
  • 子線程阻塞:thread.join(),當(dāng)子程序都結(jié)束后主程序再退出
四:多線程的寫(xiě)法
  • 實(shí)例化Threading,調(diào)用Threading的方法去進(jìn)行多線程編程
  • 寫(xiě)子類(lèi)繼承Theading,重寫(xiě)相應(yīng)的方法
    說(shuō)明:當(dāng)程序簡(jiǎn)單時(shí)可使用實(shí)例化方法,當(dāng)程序較復(fù)雜的時(shí)候,實(shí)現(xiàn)邏輯較多,第二種方法。
五:線程間通信
  • 1.共享變量:
    方法簡(jiǎn)單,也可以寫(xiě)入到單獨(dú)的py文件中。問(wèn)題:線程不安全,易出問(wèn)題。
  • 2.queue 隊(duì)列:
    使用queue 的 Queue,這個(gè)是線程安全的,多線程取數(shù)據(jù)不會(huì)出錯(cuò)。
    內(nèi)部使用的是deque Python 的雙端隊(duì)列,在字節(jié)碼的層面上就已經(jīng)到達(dá)了線程安全。
q = Queue()
# 方法:
q.put()  # 放入數(shù)據(jù)
q.get()  # 取出數(shù)據(jù)
q.put_nowait()  # 放入數(shù)據(jù),不用等待它完成再返回,異步的方法
q.get_nowait()  # 取出數(shù)據(jù),不用等待它完成再返回,異步的方法

get() put(),可以設(shè)置是否阻塞的,默認(rèn)是阻塞的

q.join()方法:
只有q.task_done()調(diào)用了join()才會(huì)讓主線程退出,成對(duì)使用。

六:線程同步
  • Lock 鎖
lock= Theading.Lock()
# 獲取鎖:
lock.acquire()
lock.release()

# 另一種方法:
with lock:
    # do something

加鎖的代碼段同時(shí)只有這一個(gè)代碼段在執(zhí)行,方式數(shù)據(jù)出問(wèn)題。
缺點(diǎn):1.用鎖會(huì)影響性能 2. 可能引起死鎖
死鎖情況:1.有acquire 沒(méi)有release 2. 相互等待

  • RLock 可重入鎖
    當(dāng)在一個(gè)線程中多個(gè)地方需要加鎖的時(shí)候用Lock 是不行的,需要用到RLock ,但是要注意的是獲取和釋放鎖的數(shù)量要一致,成對(duì)出現(xiàn)。
  • Condition 條件變量
    用于復(fù)雜的線程間同步,是一個(gè)同步鎖。例如:先后順序的多線程通信。
    重點(diǎn)函數(shù):wait() notify()
con = theading.Condition()
with con:
    # do something
    cond.notify()   #通知其他線程
    cond.wait()    # 等待其他線程通知
    # do something

注意:
1.先con.acquire()或者with con,獲取condition鎖,不然wait() notify() 不能用
2.Condition 有兩把鎖:一把底層鎖會(huì)在線程調(diào)用了wait() 的時(shí)候釋放,上面的鎖會(huì)在每次調(diào)用wait的時(shí)候分配一把并放入condition的等待隊(duì)列中,等到notify()的喚醒

  • Semaphore 信號(hào)量
    用于控制某段代碼進(jìn)入線程的數(shù)量,比如控制爬蟲(chóng)的并發(fā)量。
import threading
import time


class HtmlSppier(threading.Thread):
    def __init__(self, url, sem):
        super().__init__()
        self.sem = sem
        self.url = url

    def run(self):
        time.sleep(2)
        print('download html success')
        self.sem.release()

class UrlProducer(threading.Thread):
    def __init__(self,sem):
        super().__init__()
        self.sem = sem


    def run(self):
        for i in range(20):
            self.sem.acquire()
            html_thread = HtmlSppier(f'http://www.qq.com/pn={i}',self.sem)
            html_thread.start()


if __name__ == '__main__':
    sem = threading.Semaphore(3)
    url_produce = UrlProducer(sem)
    url_produce.start()
七:線程池

為什么要使用線程池?
主線程中可以獲取某一個(gè)線程的狀態(tài)或者某一個(gè)的任務(wù)的狀態(tài)以及返回值
當(dāng)一個(gè)線程完成的時(shí)候主線程能立即知道

import requests

def download_html(i):
    url = f'https://www.baidu.com/s?ie=UTF-8&wd={i}'
    response = requests.get(url).text
    print(response)

ids = list(range(100))


# 線程池方式一:
import threadpool
def thread_main(item):
    pool = threadpool.ThreadPool(30)
    tasks = threadpool.makeRequests(download_html, ids)
    [pool.putRequest(req) for req in tasks]
    pool.wait()


# 線程池方式二:
from multiprocessing.dummy import   Pool as thpool

def thread_pool(item):
    pool = thpool(20)
    pool.map(download_html, ids)
    pool.close()
    pool.join()


# 線程池方式三(推薦):
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=8) as exe:
    exe.map(download_html,ids)

推薦使用 concurrent.futures 模塊,線程池和進(jìn)程池的接口很相似,方便使用。

ThreadPoolExecutor 其他方法使用:
# 其他接口使用:
from concurrent.futures import ThreadPoolExecutor, as_completed,wait


executor = ThreadPoolExecutor(max_workers=8)

# 通過(guò) submit 提交執(zhí)行的函數(shù)到線程中
task1 = executor.submit(download_html, (1))
task2 = executor.submit(download_html, (3))

# done() 判斷 task 是否完成
print(task1.done())
time.sleep(4)
print(task1.done())

# result() 獲取 task 的執(zhí)行結(jié)果 阻塞
print(task1.result())

# cancel() 取消任務(wù),如果任務(wù)在執(zhí)行中或者執(zhí)行完了是不能取消的
# 現(xiàn)在線程池是8 兩個(gè)任務(wù)都會(huì)被提交任務(wù)去執(zhí)行,如果 max_workers = 1,執(zhí)行task2.cancel()就會(huì)成功取消
print(task2.cancel())


# as_completed() 獲取已經(jīng)成功的task的返回?cái)?shù)據(jù),阻塞
# as_completed實(shí)際上是一個(gè)生成器,里面有 yield 會(huì)把已經(jīng)完成的 future (task) 返回結(jié)果
ids = list(range(10))
all_task = [executor.submit(download_html,(i)) for i in ids]
time.sleep(8)
# 這是異步的,誰(shuí)完成就處理誰(shuí)
for future in as_completed(all_task):
    data = future.result()
    print(f'html response {data}')


# 通過(guò) executor 獲取已經(jīng)完成的task
for data in executor.map(download_html,ids):
    print(f'html response {data}')


# wait() 等待task完成
ids = list(range(10))
all_task = [executor.submit(download_html,(i)) for i in ids]

#  wait 的 return_when 可選項(xiàng)
FIRST_COMPLETED = 'FIRST_COMPLETED'
FIRST_EXCEPTION = 'FIRST_EXCEPTION'
ALL_COMPLETED = 'ALL_COMPLETED'
_AS_COMPLETED = '_AS_COMPLETED'

wait(all_task, return_when=ALL_COMPLETED)
八:總結(jié)

Python 多線程首選concurrent.futures 中的 ThreadPoolExecutor,使用簡(jiǎn)單方便,而且切換多進(jìn)程也是很快速的,后面繼續(xù)記錄多進(jìn)程方面的知識(shí)點(diǎn)。
代碼位置:github.com/rieuse/learnPython

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進(jìn)行...
    月亮是我踢彎得閱讀 6,144評(píng)論 3 28
  • 進(jìn)程與線程的區(qū)別 現(xiàn)在,多核CPU已經(jīng)非常普及了,但是,即使過(guò)去的單核CPU,也可以執(zhí)行多任務(wù)。由于CPU執(zhí)行代碼...
    蘇糊閱讀 842評(píng)論 0 2
  • 接著上節(jié) atomic,本節(jié)主要介紹condition_varible的內(nèi)容,練習(xí)代碼地址。本文參考http://...
    jorion閱讀 8,623評(píng)論 0 7
  • 現(xiàn)在到了綿陽(yáng),看著外面燈火通明想到你,內(nèi)心真是孤獨(dú)。孤獨(dú)的甚至到想哭。一個(gè)人這么久了好不容易走出來(lái)了,又碰到你出現(xiàn)...
    就叫王莉吧閱讀 362評(píng)論 0 0
  • 我想這一個(gè)問(wèn)題大家也是很苦惱的,人們都說(shuō)歲月不饒人,歲月是一把殺豬刀。但在這幾個(gè)男神的身上時(shí)間仿佛停止了。我們看到...
    6b127908c32d閱讀 899評(píng)論 0 0

友情鏈接更多精彩內(nèi)容