最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你搞懂Python中死鎖問題的預(yù)防與處理

 更新時(shí)間:2026年03月02日 09:46:23   作者:銘淵老黃  
死鎖(Deadlock)不像崩潰那樣嘈雜,不像內(nèi)存泄漏那樣留下痕跡,它只是讓你的程序永遠(yuǎn)等待下去,優(yōu)雅而致命,下面我們就來看看如何在Python中識(shí)別,預(yù)防和處理死鎖吧

一、那個(gè)讓服務(wù)器沉默的夜晚

凌晨?jī)牲c(diǎn),告警短信把你從睡夢(mèng)中驚醒:線上服務(wù)無響應(yīng),所有請(qǐng)求超時(shí)。你登上服務(wù)器,CPU 空轉(zhuǎn),內(nèi)存正常,日志停在某個(gè)普通的時(shí)間戳——然后什么都沒有了。

重啟服務(wù),恢復(fù)正常。第二天復(fù)盤,你發(fā)現(xiàn)是兩個(gè)線程互相等待對(duì)方釋放鎖,誰都沒有辦法繼續(xù),系統(tǒng)就這樣安靜地"死"了。

這就是死鎖(Deadlock)。它不像崩潰那樣嘈雜,不像內(nèi)存泄漏那樣留下痕跡,它只是讓你的程序永遠(yuǎn)等待下去,優(yōu)雅而致命。

Python 并發(fā)編程的實(shí)戰(zhàn)能力,很大程度上體現(xiàn)在你能否識(shí)別、預(yù)防和處理死鎖。今天這篇文章,我們從理論到代碼,徹底把死鎖這個(gè)話題講透。

二、死鎖的四個(gè)必要條件

1965 年,計(jì)算機(jī)科學(xué)家 Coffman 等人總結(jié)出死鎖發(fā)生的四個(gè)必要條件——缺少任何一個(gè),死鎖都不會(huì)發(fā)生。這四個(gè)條件是我們所有預(yù)防策略的理論基礎(chǔ)。

條件一:互斥(Mutual Exclusion)

資源在某一時(shí)刻只能被一個(gè)線程持有。鎖本身就是互斥的體現(xiàn)——這是鎖存在的意義,通常無法消除。

條件二:持有并等待(Hold and Wait)

線程已經(jīng)持有至少一個(gè)資源,同時(shí)在等待獲取其他資源。這是死鎖最典型的姿態(tài)。

條件三:不可剝奪(No Preemption)

線程持有的資源不能被強(qiáng)制剝奪,只能由持有者主動(dòng)釋放。操作系統(tǒng)層面的鎖通常滿足此條件。

條件四:循環(huán)等待(Circular Wait)

存在一個(gè)線程等待鏈:線程 A 等待線程 B 持有的資源,線程 B 等待線程 C 持有的資源……線程 N 等待線程 A 持有的資源,形成一個(gè)環(huán)。

讓我們用代碼重現(xiàn)經(jīng)典的死鎖場(chǎng)景:

import threading
import time

lock_a = threading.Lock()
lock_b = threading.Lock()

def thread_1():
    print("線程1: 嘗試獲取 lock_a...")
    with lock_a:
        print("線程1: 已持有 lock_a,等待 lock_b...")
        time.sleep(0.1)  # 模擬處理時(shí)間,給線程2機(jī)會(huì)持有 lock_b
        with lock_b:     # ← 此處永遠(yuǎn)等待,lock_b 被線程2持有
            print("線程1: 同時(shí)持有 lock_a 和 lock_b")

def thread_2():
    print("線程2: 嘗試獲取 lock_b...")
    with lock_b:
        print("線程2: 已持有 lock_b,等待 lock_a...")
        time.sleep(0.1)
        with lock_a:     # ← 此處永遠(yuǎn)等待,lock_a 被線程1持有
            print("線程2: 同時(shí)持有 lock_a 和 lock_b")

t1 = threading.Thread(target=thread_1, name="Thread-1")
t2 = threading.Thread(target=thread_2, name="Thread-2")
t1.start()
t2.start()
t1.join()  # 主線程永遠(yuǎn)阻塞在這里
t2.join()
print("這行代碼永遠(yuǎn)不會(huì)被執(zhí)行")  # ← 程序已死鎖

四個(gè)條件全部滿足:鎖是互斥的(條件1),每個(gè)線程持有一個(gè)鎖并等待另一個(gè)(條件2),鎖不會(huì)被強(qiáng)制釋放(條件3),T1→T2→T1 形成循環(huán)等待(條件4)。

三、死鎖的檢測(cè):發(fā)現(xiàn)已經(jīng)發(fā)生的死鎖

在工程實(shí)踐中,有時(shí)死鎖難以完全避免,這時(shí)就需要檢測(cè)機(jī)制——定期掃描系統(tǒng)狀態(tài),發(fā)現(xiàn)死鎖后采取恢復(fù)措施。

資源分配圖檢測(cè)算法

死鎖檢測(cè)的核心是構(gòu)建資源分配圖,然后查找圖中的環(huán)。

from collections import defaultdict
from typing import Optional


class DeadlockDetector:
    """
    基于資源分配圖的死鎖檢測(cè)器。
    維護(hù)兩種關(guān)系:
    - allocation: 線程當(dāng)前持有的鎖(已分配)
    - waiting: 線程正在等待的鎖
    """

    def __init__(self):
        # {thread_id: lock_id}  線程持有的鎖
        self.allocation: dict[str, str] = {}
        # {thread_id: lock_id}  線程等待的鎖
        self.waiting: dict[str, str] = {}

    def thread_acquired(self, thread_id: str, lock_id: str):
        """記錄線程成功獲取鎖"""
        self.allocation[thread_id] = lock_id
        # 從等待列表移除
        self.waiting.pop(thread_id, None)

    def thread_waiting(self, thread_id: str, lock_id: str):
        """記錄線程正在等待鎖"""
        self.waiting[thread_id] = lock_id

    def thread_released(self, thread_id: str):
        """記錄線程釋放鎖"""
        self.allocation.pop(thread_id, None)

    def _find_lock_holder(self, lock_id: str) -> Optional[str]:
        """找到持有某個(gè)鎖的線程"""
        for tid, lid in self.allocation.items():
            if lid == lock_id:
                return tid
        return None

    def detect_deadlock(self) -> Optional[list[str]]:
        """
        檢測(cè)是否存在死鎖,返回死鎖環(huán)中的線程列表。
        算法:對(duì)每個(gè)等待中的線程,沿等待鏈追蹤,
        如果回到起點(diǎn)則發(fā)現(xiàn)死鎖。
        """
        for start_thread in self.waiting:
            visited = []
            current = start_thread

            while current is not None:
                if current in visited:
                    # 找到環(huán)!從環(huán)的起點(diǎn)截取
                    cycle_start = visited.index(current)
                    return visited[cycle_start:] + [current]
                visited.append(current)

                # 當(dāng)前線程在等待哪個(gè)鎖?
                waiting_for_lock = self.waiting.get(current)
                if not waiting_for_lock:
                    break  # 不再等待,沒有死鎖(沿此路徑)

                # 誰持有那個(gè)鎖?
                current = self._find_lock_holder(waiting_for_lock)

        return None  # 未檢測(cè)到死鎖

    def status_report(self) -> str:
        lines = ["=== 資源分配狀態(tài) ==="]
        lines.append("持有情況:")
        for tid, lid in self.allocation.items():
            waiting = self.waiting.get(tid, "無")
            lines.append(f"  {tid} 持有 {lid},等待 {waiting}")
        deadlock = self.detect_deadlock()
        if deadlock:
            lines.append(f"\n??  檢測(cè)到死鎖!環(huán)路: {' → '.join(deadlock)}")
        else:
            lines.append("\n? 未檢測(cè)到死鎖")
        return "\n".join(lines)


# 演示死鎖檢測(cè)
detector = DeadlockDetector()

# 模擬死鎖場(chǎng)景
detector.thread_acquired("Thread-1", "Lock-A")
detector.thread_acquired("Thread-2", "Lock-B")
detector.thread_waiting("Thread-1", "Lock-B")  # T1 等待被 T2 持有的 Lock-B
detector.thread_waiting("Thread-2", "Lock-A")  # T2 等待被 T1 持有的 Lock-A

print(detector.status_report())
# 輸出:
# === 資源分配狀態(tài) ===
# 持有情況:
#   Thread-1 持有 Lock-A,等待 Lock-B
#   Thread-2 持有 Lock-B,等待 Lock-A
# ??  檢測(cè)到死鎖!環(huán)路: Thread-1 → Thread-2 → Thread-1

四、死鎖的預(yù)防:破壞四個(gè)條件之一

理論上,破壞四個(gè)必要條件中的任意一個(gè),就能預(yù)防死鎖。實(shí)踐中,最常用的是以下幾種策略。

策略一:固定加鎖順序(破壞循環(huán)等待)

這是最簡(jiǎn)單也最實(shí)用的預(yù)防策略。規(guī)定所有線程必須以相同的順序獲取鎖,循環(huán)等待就不可能形成。

import threading
from contextlib import contextmanager

lock_a = threading.Lock()
lock_b = threading.Lock()

# 給每個(gè)鎖分配唯一 ID,用于排序
LOCK_ORDER = {id(lock_a): 0, id(lock_b): 1}

@contextmanager
def acquire_locks_in_order(*locks):
    """
    按固定順序獲取多個(gè)鎖,徹底消除循環(huán)等待。
    無論調(diào)用者以什么順序傳入鎖,內(nèi)部都會(huì)按 ID 排序。
    """
    sorted_locks = sorted(locks, key=lambda l: id(l))
    acquired = []
    try:
        for lock in sorted_locks:
            lock.acquire()
            acquired.append(lock)
        yield
    finally:
        # 逆序釋放
        for lock in reversed(acquired):
            lock.release()

def safe_thread_1():
    with acquire_locks_in_order(lock_a, lock_b):  # 傳入順序無關(guān)緊要
        print("線程1: 安全地同時(shí)持有兩個(gè)鎖")

def safe_thread_2():
    with acquire_locks_in_order(lock_b, lock_a):  # 傳入順序不同,但內(nèi)部會(huì)排序
        print("線程2: 安全地同時(shí)持有兩個(gè)鎖")

t1 = threading.Thread(target=safe_thread_1)
t2 = threading.Thread(target=safe_thread_2)
t1.start(); t2.start()
t1.join(); t2.join()
print("程序正常結(jié)束,沒有死鎖!")

策略二:超時(shí)獲取鎖(破壞持有并等待)

使用 lock.acquire(timeout=N) 設(shè)置獲取鎖的超時(shí)時(shí)間,超時(shí)后放棄并重試,避免無限等待。

import threading
import time
import random
import logging

logging.basicConfig(level=logging.INFO, format="%(threadName)s: %(message)s")

lock_a = threading.Lock()
lock_b = threading.Lock()

def thread_with_timeout(first_lock, second_lock, name):
    """帶超時(shí)的鎖獲取,失敗后退避重試"""
    max_retries = 5
    for attempt in range(max_retries):
        acquired_first = first_lock.acquire(timeout=0.5)
        if not acquired_first:
            logging.info(f"獲取第一個(gè)鎖超時(shí),第 {attempt+1} 次重試")
            time.sleep(random.uniform(0.01, 0.1))  # 隨機(jī)退避,避免活鎖
            continue

        try:
            acquired_second = second_lock.acquire(timeout=0.5)
            if not acquired_second:
                logging.info(f"獲取第二個(gè)鎖超時(shí),釋放第一個(gè)鎖,第 {attempt+1} 次重試")
                first_lock.release()
                time.sleep(random.uniform(0.01, 0.1))
                continue

            try:
                # 成功同時(shí)持有兩個(gè)鎖
                logging.info(f"? 成功獲取兩個(gè)鎖,執(zhí)行臨界區(qū)操作")
                time.sleep(0.2)  # 模擬操作
                return True
            finally:
                second_lock.release()
        finally:
            if first_lock.locked():
                try:
                    first_lock.release()
                except RuntimeError:
                    pass

    logging.warning(f"??  達(dá)到最大重試次數(shù),放棄執(zhí)行")
    return False

t1 = threading.Thread(target=thread_with_timeout, args=(lock_a, lock_b, "T1"), name="Thread-1")
t2 = threading.Thread(target=thread_with_timeout, args=(lock_b, lock_a, "T2"), name="Thread-2")
t1.start(); t2.start()
t1.join(); t2.join()

五、銀行家算法:最優(yōu)雅的死鎖預(yù)防方案

以上策略都是"被動(dòng)防御",而銀行家算法(Banker’s Algorithm)是 Dijkstra 在 1965 年提出的主動(dòng)安全分配策略——在分配資源之前,先判斷分配后系統(tǒng)是否仍處于安全狀態(tài),如果不安全則拒絕分配。

核心概念

把操作系統(tǒng)比作一個(gè)銀行,線程是客戶,資源(鎖、內(nèi)存等)是貸款額度。銀行規(guī)則:只有在保證所有客戶最終都能得到滿足的前提下,才批準(zhǔn)貸款申請(qǐng)。

安全狀態(tài):存在一個(gè)執(zhí)行順序(安全序列),使得每個(gè)線程都能按需獲得資源并最終完成,釋放其持有的資源供后續(xù)線程使用。

from dataclasses import dataclass
from typing import Optional
import copy


@dataclass
class SystemState:
    """系統(tǒng)資源狀態(tài)快照"""
    n_threads: int          # 線程數(shù)量
    n_resources: int        # 資源類型數(shù)量
    available: list[int]    # 當(dāng)前可用資源向量
    allocation: list[list[int]]  # allocation[i][j]:線程i持有資源j的數(shù)量
    max_need: list[list[int]]    # max_need[i][j]:線程i最多需要資源j的數(shù)量

    @property
    def need(self) -> list[list[int]]:
        """計(jì)算每個(gè)線程還需要的資源量 = max_need - allocation"""
        return [
            [self.max_need[i][j] - self.allocation[i][j]
             for j in range(self.n_resources)]
            for i in range(self.n_threads)
        ]


class BankersAlgorithm:
    """
    銀行家算法實(shí)現(xiàn)。
    在分配資源前,模擬分配后的狀態(tài),
    若系統(tǒng)仍處于安全狀態(tài)則批準(zhǔn),否則拒絕。
    """

    def __init__(self, state: SystemState):
        self.state = state
        self.thread_names = [f"Thread-{i}" for i in range(state.n_threads)]

    def find_safe_sequence(self, state: SystemState) -> Optional[list[int]]:
        """
        安全性算法:尋找安全執(zhí)行序列。
        使用貪心策略:找到一個(gè)當(dāng)前需求能被滿足的線程,
        模擬其執(zhí)行完畢并釋放資源,重復(fù)直到所有線程完成。
        """
        available = state.available.copy()
        need = state.need
        finished = [False] * state.n_threads
        safe_sequence = []

        for _ in range(state.n_threads):
            # 在未完成的線程中,找到需求能被當(dāng)前可用資源滿足的
            found = False
            for i in range(state.n_threads):
                if finished[i]:
                    continue
                # 檢查線程 i 的所有資源需求是否都能被滿足
                if all(need[i][j] <= available[j] for j in range(state.n_resources)):
                    # 可以滿足:模擬線程 i 執(zhí)行完畢,釋放其持有的資源
                    for j in range(state.n_resources):
                        available[j] += state.allocation[i][j]
                    finished[i] = True
                    safe_sequence.append(i)
                    found = True
                    break

            if not found:
                # 這輪沒有任何線程能推進(jìn),系統(tǒng)處于不安全狀態(tài)
                return None

        return safe_sequence  # 返回安全序列

    def request_resources(self, thread_id: int, request: list[int]) -> tuple[bool, str]:
        """
        資源請(qǐng)求算法:線程申請(qǐng)資源。
        返回 (是否批準(zhǔn), 原因說明)
        """
        need = self.state.need
        thread_name = self.thread_names[thread_id]

        # 步驟1:檢查請(qǐng)求是否超過聲明的最大需求
        if any(request[j] > need[thread_id][j] for j in range(self.state.n_resources)):
            return False, f"? 拒絕:{thread_name} 的請(qǐng)求超過其聲明的最大需求"

        # 步驟2:檢查請(qǐng)求是否超過當(dāng)前可用資源
        if any(request[j] > self.state.available[j] for j in range(self.state.n_resources)):
            return False, f"? 等待:{thread_name} 請(qǐng)求的資源暫不足,需要等待"

        # 步驟3:試探性分配,檢查分配后是否仍處于安全狀態(tài)
        # 創(chuàng)建試探狀態(tài)(深拷貝)
        trial_state = copy.deepcopy(self.state)
        for j in range(self.state.n_resources):
            trial_state.available[j] -= request[j]
            trial_state.allocation[thread_id][j] += request[j]
            trial_state.max_need[thread_id][j] -= request[j]  # 更新剩余需求

        safe_sequence = self.find_safe_sequence(trial_state)

        if safe_sequence is None:
            return False, (
                f"?? 拒絕:分配給 {thread_name} 后系統(tǒng)將進(jìn)入不安全狀態(tài),"
                f"存在死鎖風(fēng)險(xiǎn)"
            )

        # 分配安全,提交試探狀態(tài)
        self.state = trial_state
        seq_names = [self.thread_names[i] for i in safe_sequence]
        return True, (
            f"? 批準(zhǔn):分配后系統(tǒng)仍安全,"
            f"安全執(zhí)行序列: {' → '.join(seq_names)}"
        )

    def status_report(self):
        """打印系統(tǒng)當(dāng)前狀態(tài)"""
        need = self.state.need
        print("\n" + "="*60)
        print("系統(tǒng)資源狀態(tài)報(bào)告")
        print("="*60)
        print(f"可用資源:  {self.state.available}")
        print(f"\n{'線程':<12} {'已分配':<20} {'最大需求':<20} {'還需要':<20}")
        print("-"*60)
        for i in range(self.state.n_threads):
            print(
                f"{self.thread_names[i]:<12} "
                f"{str(self.state.allocation[i]):<20} "
                f"{str(self.state.max_need[i]):<20} "
                f"{str(need[i]):<20}"
            )
        safe_seq = self.find_safe_sequence(self.state)
        if safe_seq:
            seq_names = [self.thread_names[i] for i in safe_seq]
            print(f"\n安全狀態(tài): ?  安全序列: {' → '.join(seq_names)}")
        else:
            print("\n安全狀態(tài): ??  不安全!存在死鎖風(fēng)險(xiǎn)")
        print("="*60 + "\n")

完整演示

# 構(gòu)建初始系統(tǒng)狀態(tài)
# 3 個(gè)線程,4 種資源(A, B, C, D)
initial_state = SystemState(
    n_threads=3,
    n_resources=4,
    available=[3, 2, 2, 1],       # 當(dāng)前可用資源
    allocation=[                    # 當(dāng)前已分配
        [0, 1, 0, 0],              # Thread-0 已持有
        [2, 0, 1, 1],              # Thread-1 已持有
        [0, 0, 1, 0],              # Thread-2 已持有
    ],
    max_need=[                      # 最大需求聲明
        [3, 2, 1, 1],              # Thread-0 最多需要
        [4, 1, 2, 2],              # Thread-1 最多需要
        [1, 1, 2, 0],              # Thread-2 最多需要
    ],
)

banker = BankersAlgorithm(initial_state)
banker.status_report()

# 場(chǎng)景1:Thread-0 請(qǐng)求 [1, 0, 1, 0]
print("Thread-0 請(qǐng)求資源 [1, 0, 1, 0]:")
approved, reason = banker.request_resources(0, [1, 0, 1, 0])
print(reason)

# 場(chǎng)景2:Thread-1 請(qǐng)求超出需求的資源
print("\nThread-1 請(qǐng)求資源 [3, 0, 0, 0](超出聲明上限):")
approved, reason = banker.request_resources(1, [3, 0, 0, 0])
print(reason)

# 場(chǎng)景3:Thread-2 請(qǐng)求可能導(dǎo)致不安全狀態(tài)的資源
print("\nThread-2 請(qǐng)求資源 [0, 1, 1, 0]:")
approved, reason = banker.request_resources(2, [0, 1, 1, 0])
print(reason)

banker.status_report()

典型輸出:

Thread-0 請(qǐng)求資源 [1, 0, 1, 0]:
? 批準(zhǔn):分配后系統(tǒng)仍安全,安全執(zhí)行序列: Thread-2 → Thread-0 → Thread-1

Thread-1 請(qǐng)求資源 [3, 0, 0, 0](超出聲明上限):
? 拒絕:Thread-1 的請(qǐng)求超過其聲明的最大需求

Thread-2 請(qǐng)求資源 [0, 1, 1, 0]:
?? 拒絕:分配給 Thread-2 后系統(tǒng)將進(jìn)入不安全狀態(tài),存在死鎖風(fēng)險(xiǎn)

六、工程實(shí)踐中的死鎖防御工具箱

理論和算法之外,日常 Python 開發(fā)中有幾個(gè)實(shí)用工具可以直接武裝你的并發(fā)代碼。

使用 threading.RLock 防止可重入死鎖:

import threading

# 普通 Lock:同一線程兩次獲取會(huì)死鎖
# lock = threading.Lock()

# RLock(可重入鎖):同一線程可以多次獲取,不會(huì)死鎖
rlock = threading.RLock()

def recursive_function(n):
    with rlock:  # 同一線程第二次獲取 RLock,安全!
        if n > 0:
            recursive_function(n - 1)
        print(f"層級(jí) {n} 執(zhí)行完畢")

recursive_function(3)  # 正常執(zhí)行,不會(huì)死鎖

使用 threading.Semaphore 限制并發(fā)度:

import threading
import time

# 信號(hào)量:限制同時(shí)訪問某資源的線程數(shù)量
db_semaphore = threading.Semaphore(3)  # 最多3個(gè)線程同時(shí)訪問數(shù)據(jù)庫(kù)

def query_database(thread_id):
    with db_semaphore:
        print(f"Thread-{thread_id}: 獲得數(shù)據(jù)庫(kù)連接")
        time.sleep(1)  # 模擬查詢
        print(f"Thread-{thread_id}: 釋放數(shù)據(jù)庫(kù)連接")

threads = [threading.Thread(target=query_database, args=(i,)) for i in range(8)]
for t in threads: t.start()
for t in threads: t.join()

七、總結(jié):死鎖防御的層次體系

處理死鎖是一個(gè)需要在多個(gè)層次同時(shí)發(fā)力的工程課題,可以用"預(yù)防優(yōu)于檢測(cè),檢測(cè)優(yōu)于恢復(fù)"來概括我們的策略優(yōu)先級(jí)。

預(yù)防層是最根本的,通過固定加鎖順序破壞循環(huán)等待,是代價(jià)最小、最可靠的手段。在設(shè)計(jì)階段就約定好鎖的獲取順序,后續(xù)所有代碼遵守這個(gè)規(guī)范,絕大多數(shù)死鎖問題都能在萌芽狀態(tài)被消滅。

規(guī)避層是更智能的選擇,銀行家算法在每次資源分配時(shí)主動(dòng)判斷安全性,代價(jià)是需要維護(hù)全局資源狀態(tài),適合資源類型明確、線程數(shù)量可控的系統(tǒng)。

檢測(cè)與恢復(fù)層是兜底機(jī)制,定期運(yùn)行死鎖檢測(cè)算法,發(fā)現(xiàn)死鎖后強(qiáng)制中止一個(gè)線程(選擇代價(jià)最小的"犧牲者")并重試。

工具層則是日常編碼的護(hù)城河:用 RLock 防止可重入死鎖,用 acquire(timeout=N) 防止無限等待,用 Queue 代替手動(dòng)鎖管理,用 asyncio 的單線程協(xié)程模型從根本上消滅鎖的需求。

并發(fā)編程是 Python 開發(fā)者的成人禮。每一次與死鎖的正面交鋒,都是對(duì)你系統(tǒng)設(shè)計(jì)能力的一次考驗(yàn)。掌握這些工具和思維框架,你就有了在黑暗中點(diǎn)燈的能力。

你遇到過最難排查的死鎖是什么情況?用了什么手段最終定位到問題? 歡迎在評(píng)論區(qū)分享你的"偵探故事",這類經(jīng)驗(yàn)對(duì)所有開發(fā)者都極有價(jià)值。

到此這篇關(guān)于一文帶你搞懂Python中死鎖問題的預(yù)防與處理的文章就介紹到這了,更多相關(guān)Python死鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python讀取excel數(shù)據(jù)繪制簡(jiǎn)單曲線圖的完整步驟記錄

    python讀取excel數(shù)據(jù)繪制簡(jiǎn)單曲線圖的完整步驟記錄

    這篇文章主要給大家介紹了關(guān)于python讀取excel數(shù)據(jù)繪制簡(jiǎn)單曲線圖的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Python unittest discover批量執(zhí)行代碼實(shí)例

    Python unittest discover批量執(zhí)行代碼實(shí)例

    這篇文章主要介紹了Python unittest discover批量執(zhí)行代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Python?matplotlib包和gif包生成gif動(dòng)畫實(shí)戰(zhàn)對(duì)比

    Python?matplotlib包和gif包生成gif動(dòng)畫實(shí)戰(zhàn)對(duì)比

    使用matplotlib生成gif動(dòng)畫的方法相信大家應(yīng)該都看到過,下面這篇文章主要給大家介紹了關(guān)于Python?matplotlib包和gif包生成gif動(dòng)畫對(duì)比的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • Python實(shí)現(xiàn)確認(rèn)字符串是否包含指定字符串的實(shí)例

    Python實(shí)現(xiàn)確認(rèn)字符串是否包含指定字符串的實(shí)例

    下面小編就為大家分享一篇Python實(shí)現(xiàn)確認(rèn)字符串是否包含指定字符串的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • Python 內(nèi)置函數(shù) oct詳解

    Python 內(nèi)置函數(shù) oct詳解

    Python 的內(nèi)置函數(shù) oct()用于將一個(gè)整數(shù)轉(zhuǎn)換為八進(jìn)制以8為基數(shù)字符串表示形式,本文給大家介紹Python 內(nèi)置函數(shù) oct的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2025-07-07
  • python中opencv與PIL圖片讀取保存及相互轉(zhuǎn)化方式

    python中opencv與PIL圖片讀取保存及相互轉(zhuǎn)化方式

    這篇文章主要介紹了python中opencv與PIL圖片讀取保存及相互轉(zhuǎn)化方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • 使用 PyTorch-BigGraph 構(gòu)建和部署大規(guī)模圖嵌入的完整步驟

    使用 PyTorch-BigGraph 構(gòu)建和部署大規(guī)模圖嵌入的完整步驟

    本文深入探討了使用 PyTorch-BigGraph (PBG) 構(gòu)建和部署大規(guī)模圖嵌入的完整流程,涵蓋了從環(huán)境設(shè)置、數(shù)據(jù)準(zhǔn)備、模型配置與訓(xùn)練,到高級(jí)優(yōu)化技術(shù)、評(píng)估指標(biāo)、部署策略以及實(shí)際案例研究等各個(gè)方面,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • 簡(jiǎn)單了解Django ContentType內(nèi)置組件

    簡(jiǎn)單了解Django ContentType內(nèi)置組件

    這篇文章主要介紹了簡(jiǎn)單了解Django ContentType內(nèi)置組件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • Python multiprocessing模塊中的Pipe管道使用實(shí)例

    Python multiprocessing模塊中的Pipe管道使用實(shí)例

    這篇文章主要介紹了Python multiprocessing模塊中的Pipe管道使用實(shí)例,本文直接給出使用實(shí)例,需要的朋友可以參考下
    2015-04-04
  • 如何使用python爬取B站排行榜Top100的視頻數(shù)據(jù)

    如何使用python爬取B站排行榜Top100的視頻數(shù)據(jù)

    本文章向大家介紹python爬取b站排行榜,包括python爬取b站排行榜的具體代碼,對(duì)大家的學(xué)習(xí)或工作具有一定的參考價(jià)值,需要的朋友可以參考一下
    2021-09-09

最新評(píng)論

赤城县| 华坪县| 深泽县| 河北省| 大洼县| 虞城县| 长沙县| 余江县| 桐乡市| 巢湖市| 漠河县| 盐山县| 二手房| 翁源县| 三门县| 友谊县| 泾源县| 随州市| 兴安盟| 怀化市| 大石桥市| 玛纳斯县| 府谷县| 当雄县| 宝山区| 彭山县| 龙胜| 贵阳市| 灵丘县| 潍坊市| 马边| 临武县| 江安县| 温泉县| 宜兴市| 洪湖市| 邵东县| 彭山县| 蕉岭县| 乐昌市| 应城市|