国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

GO 中如何防止 goroutine 泄露

這篇具有很好參考價(jià)值的文章主要介紹了GO 中如何防止 goroutine 泄露。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

GO 中如何防止 goroutine 泄露,go,程序員,并發(fā)程序


今天來(lái)簡(jiǎn)單談?wù)?,Go 如何防止 goroutine 泄露。

概述

Go 的并發(fā)模型與其他語(yǔ)言不同,雖說它簡(jiǎn)化了并發(fā)程序的開發(fā)難度,但如果不了解使用方法,常常會(huì)遇到 goroutine 泄露的問題。雖然 goroutine 是輕量級(jí)的線程,占用資源很少,但如果一直得不到釋放并且還在不斷創(chuàng)建新協(xié)程,毫無(wú)疑問是有問題的,并且是要在程序運(yùn)行幾天,甚至更長(zhǎng)的時(shí)間才能發(fā)現(xiàn)的問題。

對(duì)于上面描述的問題,我覺得可以從兩方面入手解決,如下:

一是預(yù)防,要做到預(yù)防,我們就需要了解什么樣的代碼會(huì)產(chǎn)生泄露,以及了解如何寫出正確的代碼;

二是監(jiān)控,雖說預(yù)防減少了泄露產(chǎn)生的概率,但沒有人敢說自己不犯錯(cuò),因而,通常我們還需要一些監(jiān)控手段進(jìn)一步保證程序的健壯性;

接下來(lái),我將會(huì)分兩篇文章分別從這兩個(gè)角度進(jìn)行介紹,今天先談第一點(diǎn)。

如何監(jiān)控泄露

本文主要集中在第一點(diǎn)上,但為了更好的演示效果,可以先介紹一個(gè)最簡(jiǎn)單的監(jiān)控方式。通過 runtime.NumGoroutine() 獲取當(dāng)前運(yùn)行中的 goroutine 數(shù)量,通過它確認(rèn)是否發(fā)生泄漏。它的使用非常簡(jiǎn)單,就不為它專門寫個(gè)例子了。

一個(gè)簡(jiǎn)單的例子

語(yǔ)言級(jí)別的并發(fā)支持是 Go 的一大優(yōu)勢(shì),但這個(gè)優(yōu)勢(shì)也很容易被濫用。通常我們?cè)陂_始 Go 并發(fā)學(xué)習(xí)時(shí),常常聽別人說,Go 的并發(fā)非常簡(jiǎn)單,在調(diào)用函數(shù)前加上 go 關(guān)鍵詞便可啟動(dòng) goroutine,即一個(gè)并發(fā)單元,但很多人可能只聽到了這句話,然后就出現(xiàn)了類似下面的代碼:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func sayHello() {
    for {
        fmt.Println("Hello gorotine")
        time.Sleep(time.Second)
    }
}

func main() {
    defer func() {
        fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
    }()

    go sayHello()
    fmt.Println("Hello main")
}

對(duì) Go 比較熟悉的話,很容易發(fā)現(xiàn)這段代碼的問題,sayHello 是個(gè)死循環(huán),沒有如何退出機(jī)制,因此也就沒有任何辦法釋放創(chuàng)建的 goroutine。我們通過在 main 函數(shù)最前面的 defer 實(shí)現(xiàn)在函數(shù)退出時(shí)打印當(dāng)前運(yùn)行中的 goroutine 數(shù)量,毫無(wú)意外,它的輸出如下:

the number of goroutines: 2

不過,因?yàn)樯厦娴某绦虿⒎浅qv,有泄露問題也不大,程序退出后系統(tǒng)會(huì)自動(dòng)回收運(yùn)行時(shí)資源。但如果這段代碼在常駐服務(wù)中執(zhí)行,比如 http server,每接收到一個(gè)請(qǐng)求,便會(huì)啟動(dòng)一次 sayHello,時(shí)間流逝,每次啟動(dòng)的 goroutine 都得不到釋放,你的服務(wù)將會(huì)離奔潰越來(lái)越近。

這個(gè)例子比較簡(jiǎn)單,我相信,對(duì) Go 的并發(fā)稍微有點(diǎn)了解的朋友都不會(huì)犯這個(gè)錯(cuò)。

泄露情況分類

前面介紹的例子由于在 goroutine 運(yùn)行死循環(huán)導(dǎo)致的泄露。接下來(lái),我會(huì)按照并發(fā)的數(shù)據(jù)同步方式對(duì)泄露的各種情況進(jìn)行分析。簡(jiǎn)單可歸于兩類,即:

  • channel 導(dǎo)致的泄露
  • 傳統(tǒng)同步機(jī)制導(dǎo)致的泄露

傳統(tǒng)同步機(jī)制主要指面向共享內(nèi)存的同步機(jī)制,比如排它鎖、共享鎖等。這兩種情況導(dǎo)致的泄露還是比較常見的。go 由于 defer 的存在,第二類情況,一般情況下還是比較容易避免的。

chanel 引起的泄露

先說 channel,如果之前讀過官方的那篇并發(fā)的文章,翻譯版,你會(huì)發(fā)現(xiàn) channel 的使用,一個(gè)不小心就泄露了。我們來(lái)具體總結(jié)下那些情況下可能導(dǎo)致。

發(fā)送不接收

我們知道,發(fā)送者一般都會(huì)配有相應(yīng)的接收者。理想情況下,我們希望接收者總能接收完所有發(fā)送的數(shù)據(jù),這樣就不會(huì)有任何問題。但現(xiàn)實(shí)是,一旦接收者發(fā)生異常退出,停止繼續(xù)接收上游數(shù)據(jù),發(fā)送者就會(huì)被阻塞。這個(gè)情況在 前面說的文章 中有非常細(xì)致的介紹。

示例代碼:

package main

import "time"

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func main() {
    defer func() {
        fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
    }()

    // Set up the pipeline.
    out := gen(2, 3)

    for n := range out {
        fmt.Println(n)               // 2
        time.Sleep(5 * time.Second) // done thing, 可能異常中斷接收
        if true { // if err != nil 
            break
        }
    }
}

例子中,發(fā)送者通過 out chan 向下游發(fā)送數(shù)據(jù),main 函數(shù)接收數(shù)據(jù),接收者通常會(huì)依據(jù)接收到的數(shù)據(jù)做一些具體的處理,這里用 Sleep 代替。如果這期間發(fā)生異常,導(dǎo)致處理中斷,退出循環(huán)。gen 函數(shù)中啟動(dòng)的 goroutine 并不會(huì)退出。

如何解決?

此處的主要問題在于,當(dāng)接收者停止工作,發(fā)送者并不知道,還在傻傻地向下游發(fā)送數(shù)據(jù)。故而,我們需要一種機(jī)制去通知發(fā)送者。我直接說答案吧,就不循漸進(jìn)了。Go 可以通過 channel 的關(guān)閉向所有的接收者發(fā)送廣播信息。

修改后的代碼:

package main

import "time"

func gen(done chan struct{}, nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            select {
            case out <- n:
            case <-done:
                return
            }
        }
    }()
    return out
}

func main() {
    defer func() {
        time.Sleep(time.Second)
        fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
    }()

    // Set up the pipeline.
    done := make(chan struct{})
    defer close(done)

    out := gen(done, 2, 3)

    for n := range out {
        fmt.Println(n) // 2
        time.Sleep(5 * time.Second) // done thing, 可能異常中斷接收
        if true { // if err != nil 
            break
        }
    }
}

函數(shù) gen 中通過 select 實(shí)現(xiàn) 2 個(gè) channel 的同時(shí)處理。當(dāng)異常發(fā)生時(shí),將進(jìn)入 <-done 分支,實(shí)現(xiàn) goroutine 退出。這里為了演示效果,保證資源順利釋放,退出時(shí)等待了幾秒保證釋放完成。

執(zhí)行后的輸出如下:

the number of goroutines:  1

現(xiàn)在只有主 goroutine 存在。

接收不發(fā)送

發(fā)送不接收會(huì)導(dǎo)致發(fā)送者阻塞,反之,接收不發(fā)送也會(huì)導(dǎo)致接收者阻塞。直接看示例代碼,如下:

package main

func main() {
    defer func() {
        time.Sleep(time.Second)
        fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
    }()

    var ch chan struct{}
    go func() {
        ch <- struct{}{}
    }()
}

運(yùn)行結(jié)果顯示:

the number of goroutines:  2

當(dāng)然,我們正常不會(huì)遇到這么傻的情況發(fā)生,現(xiàn)實(shí)工作中的案例更多可能是發(fā)送已完成,但是發(fā)送者并沒有關(guān)閉 channel,接收者自然也無(wú)法知道發(fā)送完畢,阻塞因此就發(fā)生了。

解決方案是什么?那當(dāng)然就是,發(fā)送完成后一定要記得關(guān)閉 channel。

nil channel

向 nil channel 發(fā)送和接收數(shù)據(jù)都將會(huì)導(dǎo)致阻塞。這種情況可能在我們定義 channel 時(shí)忘記初始化的時(shí)候發(fā)生。

示例代碼:

func main() {
    defer func() {
        time.Sleep(time.Second)
        fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
    }()

    var ch chan int
    go func() {
        <-ch
        // ch<-
    }()
}

兩種寫法:<-ch 和 ch<- 1,分別表示接收與發(fā)送,都將會(huì)導(dǎo)致阻塞。如果想實(shí)現(xiàn)阻塞,通過 nil channel 和 done channel 結(jié)合實(shí)現(xiàn)阻止 main 函數(shù)的退出,這或許是可以一試的方法。

func main() {
	defer func() {
		time.Sleep(time.Second)
		fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
	}()

	done := make(chan struct{})

	var ch chan int
	go func() {
		defer close(done)
	}()

	select {
	case <-ch:
	case <-done:
		return
	}
}

在 goroutine 執(zhí)行完成,檢測(cè)到 done 關(guān)閉,main 函數(shù)退出。

真實(shí)的場(chǎng)景

真實(shí)的場(chǎng)景肯定不會(huì)像案例中的簡(jiǎn)單,可能涉及多階段 goroutine 之間的協(xié)作,某個(gè) goroutine 可能即使接收者又是發(fā)送者。但歸根接底,無(wú)論什么使用模式。都是把基礎(chǔ)知識(shí)組織在一起的合理運(yùn)用。

傳統(tǒng)同步機(jī)制

雖然,一般推薦 Go 并發(fā)數(shù)據(jù)的傳遞,但有些場(chǎng)景下,顯然還是使用傳統(tǒng)同步機(jī)制更合適。Go 中提供傳統(tǒng)同步機(jī)制主要在 sync 和 atomic 兩個(gè)包。接下來(lái),我主要介紹的是鎖和 WaitGroup 可能導(dǎo)致 goroutine 的泄露。

Mutex

和其他語(yǔ)言類似,Go 中存在兩種鎖,排它鎖和共享鎖,關(guān)于它們的使用就不作介紹了。我們以排它鎖為例進(jìn)行分析。

示例如下:

func main() {
    total := 0

    defer func() {
        time.Sleep(time.Second)
        fmt.Println("total: ", total)
        fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
    }()

    var mutex sync.Mutex
    for i := 0; i < 2; i++ {
        go func() {
            mutex.Lock()
            total += 1
        }()
    }
}

執(zhí)行結(jié)果如下:

total: 1
the number of goroutines: 2

這段代碼通過啟動(dòng)兩個(gè) goroutine 對(duì) total 進(jìn)行加法操作,為防止出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng),對(duì)計(jì)算部分做了加鎖保護(hù),但并沒有及時(shí)的解鎖,導(dǎo)致 i = 1 的 goroutine 一直阻塞等待 i = 0 的 goroutine 釋放鎖。可以看到,退出時(shí)有 2 個(gè) goroutine 存在,出現(xiàn)了泄露,total 的值為 1。

怎么解決?因?yàn)?Go 有 defer 的存在,這個(gè)問題還是非常容易解決的,只要記得在 Lock 的時(shí)候,記住 defer Unlock 即可。

示例如下:

mutex.Lock()
defer mutext.Unlock()

其他的鎖與這里其實(shí)都是類似的。

WaitGroup

WaitGroup 和鎖有所差別,它類似 Linux 中的信號(hào)量,可以實(shí)現(xiàn)一組 goroutine 操作的等待。使用的時(shí)候,如果設(shè)置了錯(cuò)誤的任務(wù)數(shù),也可能會(huì)導(dǎo)致阻塞,導(dǎo)致泄露發(fā)生。

一個(gè)例子,我們?cè)陂_發(fā)一個(gè)后端接口時(shí)需要訪問多個(gè)數(shù)據(jù)表,由于數(shù)據(jù)間沒有依賴關(guān)系,我們可以并發(fā)訪問,示例如下:

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

func handle() {
    var wg sync.WaitGroup

    wg.Add(4)

    go func() {
        fmt.Println("訪問表1")
        wg.Done()
    }()

    go func() {
        fmt.Println("訪問表2")
        wg.Done()
    }()

    go func() {
        fmt.Println("訪問表3")
        wg.Done()
    }()

    wg.Wait()
}

func main() {
    defer func() {
        time.Sleep(time.Second)
        fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
    }()

    go handle()
    time.Sleep(time.Second)
}

執(zhí)行結(jié)果如下:

the number of goroutines: 2

出現(xiàn)了泄露。再看代碼,它的開始部分定義了類型為 sync.WaitGroup 的變量 wg,設(shè)置并發(fā)任務(wù)數(shù)為 4,但是從例子中可以看出只有 3 個(gè)并發(fā)任務(wù)。故最后的 wg.Wait() 等待退出條件將永遠(yuǎn)無(wú)法滿足,handle 將會(huì)一直阻塞。

怎么防止這類情況發(fā)生?

我個(gè)人的建議是,盡量不要一次設(shè)置全部任務(wù)數(shù),即使數(shù)量非常明確的情況。因?yàn)樵陂_始多個(gè)并發(fā)任務(wù)之間或許也可能出現(xiàn)被阻斷的情況發(fā)生。最好是盡量在任務(wù)啟動(dòng)時(shí)通過 wg.Add(1) 的方式增加。

示例如下:

    ...
    wg.Add(1)
    go func() {
        fmt.Println("訪問表1")
        wg.Done()
    }()

    wg.Add(1)
    go func() {
        fmt.Println("訪問表2")
        wg.Done()
    }()

    wg.Add(1)
    go func() {
        fmt.Println("訪問表3")
        wg.Done()
    }()
    ...

總結(jié)

大概介紹完了我認(rèn)為的所有可能導(dǎo)致 goroutine 泄露的情況。總結(jié)下來(lái),其實(shí)無(wú)論是死循環(huán)、channel 阻塞、鎖等待,只要是會(huì)造成阻塞的寫法都可能產(chǎn)生泄露。因而,如何防止 goroutine 泄露就變成了如何防止發(fā)生阻塞。為進(jìn)一步防止泄露,有些實(shí)現(xiàn)中會(huì)加入超時(shí)處理,主動(dòng)釋放處理時(shí)間太長(zhǎng)的 goroutine。

本篇主要從如何寫出正確代碼的角度來(lái)介紹如何防止 goroutine 的泄露。下篇,將會(huì)介紹如何實(shí)現(xiàn)更好的監(jiān)控檢測(cè),以幫助我們發(fā)現(xiàn)當(dāng)前代碼中已經(jīng)存在的泄露。

參考資料

Concurrency In Go
Goroutine leak
Leaking-Goroutines
Go Concurrency Patterns: Context
Go Concurrency Patterns: Pipelines and cancellation
make goroutine stay running after returning from function
Never start a goroutine without knowing how it will stop文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-810542.html

到了這里,關(guān)于GO 中如何防止 goroutine 泄露的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 移動(dòng)應(yīng)用數(shù)據(jù)安全性:如何防止應(yīng)用程序被黑客攻擊和數(shù)據(jù)泄露?

    移動(dòng)應(yīng)用數(shù)據(jù)安全性:如何防止應(yīng)用程序被黑客攻擊和數(shù)據(jù)泄露?

    在移動(dòng)應(yīng)用成為人們生活中不可或缺的一部分的今天,數(shù)據(jù)安全性已經(jīng)成為一個(gè)非常重要的問題。隨著黑客攻擊和數(shù)據(jù)泄露事件的頻繁發(fā)生,用戶對(duì)于移動(dòng)應(yīng)用程序的信任度也在逐漸下降。本文將探討移動(dòng)應(yīng)用數(shù)據(jù)安全性的重要性,并提供一些有效的技術(shù)措施來(lái)防止應(yīng)用程序

    2024年02月08日
    瀏覽(37)
  • 7 文件操作、單元測(cè)試、goroutine【Go語(yǔ)言教程】

    7 文件操作、單元測(cè)試、goroutine【Go語(yǔ)言教程】

    1.1 介紹 os.File 封裝所有文件相關(guān)操作,F(xiàn)ile 是一個(gè)結(jié)構(gòu)體 常用方法: 打開文件 關(guān)閉文件 1.2 應(yīng)用實(shí)例 ①讀文件 常用方法: ①bufio.NewReader(), reader.ReadString【帶緩沖】 ②io/ioutil【一次性讀取,適用于小文件】 讀取文件的內(nèi)容并顯示在終端(帶緩沖區(qū)的方式),使用 os.Open, file.

    2024年02月04日
    瀏覽(24)
  • GO語(yǔ)言網(wǎng)絡(luò)編程(并發(fā)編程)并發(fā)介紹,Goroutine

    GO語(yǔ)言網(wǎng)絡(luò)編程(并發(fā)編程)并發(fā)介紹,Goroutine

    進(jìn)程和線程 并發(fā)和并行 協(xié)程和線程 協(xié)程:獨(dú)立的??臻g,共享堆空間,調(diào)度由用戶自己控制,本質(zhì)上有點(diǎn)類似于用戶級(jí)線程,這些用戶級(jí)線程的調(diào)度也是自己實(shí)現(xiàn)的。 線程:一個(gè)線程上可以跑多個(gè)協(xié)程,協(xié)程是輕量級(jí)的線程。 goroutine 只是由官方實(shí)現(xiàn)的超級(jí)\\\"線程池\\\"。 每個(gè)

    2024年02月09日
    瀏覽(92)
  • Go學(xué)習(xí)第十一章——協(xié)程goroutine與管道channel

    Go學(xué)習(xí)第十一章——協(xié)程goroutine與管道channel

    1 協(xié)程goroutine 1.1 基本介紹 前置知識(shí):“進(jìn)程和線程”,“并發(fā)與并行” 協(xié)程的概念 協(xié)程(Coroutine)是一種用戶態(tài)的輕量級(jí)線程,不同于操作系統(tǒng)線程,協(xié)程能夠在單個(gè)線程中實(shí)現(xiàn)多任務(wù)并發(fā),使用更少的系統(tǒng)資源。協(xié)程的運(yùn)行由程序控制,不需要操作系統(tǒng)介入,因此協(xié)程之

    2024年02月08日
    瀏覽(31)
  • 聊聊程序員那些【越早知道越好】的道理或者建議-程序員如何提升自己

    比如好好學(xué)習(xí)ide的使用和快捷鍵 ,以及一些常用的命令。 寫一個(gè)實(shí)體類:Alt+Insert,shift+ ↓ ↓ ↓(或者ctrl+a全選) ,回車 把代碼封裝成方法:Ctrl + Alt + M 代碼格式化:Ctrl + Alt + L 刪除無(wú)用的導(dǎo)包:ctrl+alt+o 構(gòu)建項(xiàng)目:ctrl+f9 … 比如 mybatis generate lombok 比如引入自動(dòng)化工具,

    2023年04月24日
    瀏覽(59)
  • 程序員如何成長(zhǎng)

    程序員如何成長(zhǎng)

    做技術(shù)是打怪獸不是養(yǎng)寵物,為什么要打怪獸?因?yàn)殡y;為什么難很重要?因?yàn)殡y的事情才能帶來(lái)成長(zhǎng);為什么要成長(zhǎng)?承認(rèn)吧,因?yàn)椤溉绾纬砷L(zhǎng)」是當(dāng)代人,包括你我他在內(nèi)焦慮的源泉。 過去幾個(gè)月內(nèi)我在寫一系列主題為「NodeJS實(shí)戰(zhàn)」的文章,內(nèi)容來(lái)源是過去兩年獨(dú)自開發(fā)

    2024年02月08日
    瀏覽(26)
  • 程序員如何提高代碼能力?

    程序員如何提高代碼能力?

    作為一名程序員,自己的本質(zhì)工作就是做程序開發(fā),那么程序開發(fā)的時(shí)候最直接的體現(xiàn)就是代碼,檢驗(yàn)一個(gè)程序員技術(shù)水平的一個(gè)核心環(huán)節(jié)就是開發(fā)時(shí)候的代碼能力。眾所周知,程序開發(fā)的水平提升是一個(gè)循序漸進(jìn)的過程,每一位程序員都是從“菜鳥”變成“大神”的,所以

    2024年02月01日
    瀏覽(22)
  • 程序員該如何學(xué)習(xí)技術(shù)

    程序員該如何學(xué)習(xí)技術(shù)

    前言 ??學(xué)習(xí)是第一生產(chǎn)力,我從來(lái)都是這么認(rèn)為的,人只有只有不斷地學(xué)習(xí)才能意識(shí)到自己的缺點(diǎn)和不足,身為程序員,我更認(rèn)為人們應(yīng)當(dāng)抱著終身學(xué)習(xí)的想法實(shí)踐下去,這是我所一直踐行且相信的。 ??高處不勝寒,只有站在更高的地方,才能欣賞更多的風(fēng)景,當(dāng)年老

    2024年02月01日
    瀏覽(25)
  • 程序員如何提高代碼能力

    程序員如何提高代碼能力

    C++ 是一種功能強(qiáng)大的編程語(yǔ)言,廣泛應(yīng)用于操作系統(tǒng)、數(shù)據(jù)庫(kù)、游戲開發(fā)等領(lǐng)域。而要成為一名優(yōu)秀的 C++ 程序員,不僅需要掌握 C++ 的基本語(yǔ)法和特性,還需要不斷提高自己的代碼能力。 以下是一些具體的方法和建議,幫助你提高 C++ 代碼能力: 閱讀優(yōu)秀的代碼 閱讀優(yōu)秀代

    2023年04月25日
    瀏覽(22)
  • 程序員如何制作PPT?

    程序員如何制作PPT?

    有道無(wú)術(shù),術(shù)尚可求也;有術(shù)無(wú)道,止于術(shù)。大家好,我是程序員雪球,今天讓我們一起探討如何從零開始制作高質(zhì)量的 PPT。 上周,領(lǐng)導(dǎo)要求我撰寫一份關(guān)于 4到6月持續(xù)集成運(yùn)營(yíng)分析的報(bào)告,并通過 PPT 的形式進(jìn)行匯報(bào)。作為一名五年經(jīng)驗(yàn)的老程序員,我深知 PPT 制作并非我

    2024年02月16日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包