主頁 > 後端開發 > 如何優雅得關閉協程呢

如何優雅得關閉協程呢

2023-05-15 07:24:22 後端開發

1.簡介

本文將介紹首先為什么需要主動關閉goroutine,并介紹如何在Go語言中關閉goroutine的常見套路,包括傳遞終止信號和協程內部捕捉終止信號,之后,文章列舉了需要主動關閉協程運行的常見場景,如啟動一個協程執行一個不斷重復的任務,希望通過本文的介紹,讀者能夠掌握如何在適當的時候關閉goroutine,以及了解關閉goroutine的常見套路,

2.為什么需要關閉goroutine

2.1 協程的生命周期

了解協程的生命周期是優雅地關閉協程的前提,因為在關閉協程之前需要知道協程的當前狀態,以便采取相應的措施,所以這里我們需要先了解下goroutine的生命周期,

Go語言中,協程(goroutine)是一種輕量級的執行緒,可以在一個程式中同時運行多個協程,提高程式的并發性能,協程的生命周期包括創建、運行和結束三個階段,

首先需要創建一個協程,協程的創建可以通過關鍵字 go 來實作,例如:

go func() {
    // 協程執行的代碼
}()

上面的代碼會啟動一個新的協程,同時在新的協程中執行匿名函式,此時協程便已被創建了,

一旦協程被創建,它就會在新的執行緒中運行,協程的運行狀態可以由 Go 運行時(goroutine scheduler)來管理,它會自動將協程調度到適當的P中運行,并確保協程的公平調度和平衡負載,

在運行階段,協程會不斷地執行任務,直到任務完成或者遇到終止條件,在終止階段,協程將會被回收,從而完成其整個生命周期,

綜上所述,協程由go關鍵字啟動,在協程中執行其業務邏輯,直到最后遇到終止條件,此時代表著協程的任務已經結束了,將進入終止階段,最終協程將會被回收,

2.2 協程的終止條件

正常來說,都是協程任務執行完成之后,此時協程自動退出,例如:

func main() {
   var wg sync.WaitGroup
   wg.Add(1)
   go func() {
      defer wg.Done()
      // 協程執行的代碼
      fmt.Println("協程執行完畢")
   }()
   wg.Wait()
   // 等待協程執行完畢
   fmt.Println("主程式結束")

上面的代碼中,我們使用 WaitGroup 等待協程執行完畢,在協程執行完畢后,程式會輸出協程執行完畢和主程式結束兩條資訊,

還有一種情況是協程發生panic,它將會自動退出,例如:

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        // 協程執行的代碼
        panic("協程發生錯誤")
    }()
    // 等待協程執行完畢
    wg.Wait()
    fmt.Println("主程式結束")
}

在這種情況下,協程也會自動退出,不會再占用系統資源,

綜合看來,協程的終止條件,其實就是協程中的任務執行完成了,或者是執行程序中發生了panic,協程將滿足終止條件,退出執行,

2.3 為什么需要主動關閉goroutine

從上面協程的終止條件來看,正常情況下,協程只要將任務正常處理完成,協程自動退出,此時并不需要主動關閉goroutine

這里先舉一個生產者消費者的例子,在這個例子中,我們創建了一個生產者和一個消費者,它們之間通過一個channel進行通信,生產者生產資料并發送到一個channel中,消費者從這個channel中讀取資料并進行處理,代碼示例如下:

func main() {
    // 生產者代碼
    go func(out chan<- int) {
        for i := 0; ; i++ {
            select {
            case out <- i:
                fmt.Printf("producer: produced %d\n", i)
            time.Sleep(time.Second)
        }
    }
    // 消費者邏輯
    go func(in <-chan int) {
        for {
            select {
            case i := <-in:
                fmt.Printf("consumer: consumed %d\n", i)
            }
        }
    }
    // 讓生產者協程和消費者協程一直執行下去
    time.Sleep(100000000)
}

在這個例子中,我們使用了兩個goroutine:生產者和消費者,生產者向channel中生產資料,消費者從channel中消費資料,

但是,假如生產者出現了問題,此時生產者的協程將會被退出,不再執行,而消費者仍然在等待資料的輸入,此時消費者協程已經沒有存在的必要了,其實是需要退出執行,

因此,對于一些雖然沒有達到終止條件的協程,但是其又沒有再繼續執行下去的必要,此時主動關閉其執行,從而保證程式的健壯性和性能,

3.如何優雅得關閉goroutine

優雅得關閉goroutine的執行,我們可以遵循以下三個步驟,首先是傳遞關閉協程的信號,其次是協程內部需要能夠到關閉信號,最后是協程退出時,能夠正確釋放其所占據的資源,通過以上步驟,可以保在需要時優雅地停止goroutine的執行,下面對這三個步驟詳細進行講解,

3.1 傳遞關閉終止信號

首先是通過給goroutine傳遞關閉協程的信號,從而讓協程進行退出操作,這里可以使用context.Context來傳遞信號,具體實作可以通過呼叫WithCancel,WithDeadline,WithTimeout等方法來創建一個帶有取消功能的Context,并在需要關閉協程時呼叫Cancel方法來向Context發送取消信號,示例代碼如下:

ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
    for {
        select {
        // 呼叫cancel函式后,這里將能夠收到通知
        case <-ctx.Done():
            return
        default:
            // do something
        }
    }
}(ctx)
// 在需要關閉協程時呼叫cancel方法發送取消信號
cancel()

這里,當我們想要終止協程的執行時,只需要呼叫可取消context物件的Cancel方法,協程內部將能夠通過context物件接收到終止協程執行的通知,

3.2 協程內部捕捉終止信號

協程內部也需要在取消信號傳遞過來時,能夠正確被捕捉到,才能夠正常終止流程,這里我們可以使用select陳述句來監聽取消信號,select陳述句可以有多個case子句,可以同時監聽多個channel,當select陳述句執行時,它會一直阻塞,直到有一個case子句可以執行,select陳述句也可以包含default子句,這個子句在所有的case子句都不能執行時會被執行,通常用于防止select陳述句的阻塞,如下:

select {
case <-channel:
    // channel有資料到來時執行的代碼
default:
    // 所有channel都沒有資料時執行的代碼
}

context物件的Done方法剛好也是回傳一個channel,取消信號便是通過該channel來進行傳遞的,所以我們可以在協程內部,通過select陳述句,在其中一個case分支來監聽取消信號;同時使用一個default分支在協程中執行具體的業務邏輯,在終止信號沒有到來時,就執行業務邏輯;在收到協程終止信號后,也能夠及時終止協程的執行,如下:

go func(ctx context.Context) {
    for {
        select {
        // 呼叫cancel函式后,這里將能夠收到通知
        case <-ctx.Done():
            return
        default:
            // 執行業務邏輯
        }
    }
}(ctx)

3.3 回收協程資源

最后,當協程被終止執行時,需要釋放占用的資源,包括檔案句柄、記憶體等,以便其他程式可以繼續使用這些資源,在Go語言中,可以使用defer陳述句來確保協程在退出時能夠正確地釋放資源,比如協程中打開了一個檔案,此時可以通過defer陳述句來關閉,避免資源的泄漏,代碼示例如下:

func doWork() {
    file, err := os.Open("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    // Do some work
}

在這個例子中,我們在檔案打開之后使用defer陳述句注冊了一個函式,當協程結束時會自動呼叫該函式來關閉檔案,這樣協程無論在何時退出,我們都可以確保檔案被正確關閉,避免資源泄漏和其他問題,

3.4 關閉goroutine示例

下面展示一個簡單的例子,結合Context物件,select陳述句以及defer陳述句這三部分內容,優雅得終止一個協程的運行,具體代碼示例如下:

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context) {
    // 最后,在協程退出前,釋放資源.
    defer fmt.Println("worker stopped")

    for {
        // 通過select陳述句監聽取消信號,取消信號沒到達,則執行業務邏輯,等下次回圈檢查
        select {
        default:
            fmt.Println("working")
        case <-ctx.Done():
            return
        }
        time.Sleep(time.Second)
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    // 啟動一個協程執行任務
    go worker(ctx)
    // 執行5s后,呼叫cancel函式終止協程
    time.Sleep(5 * time.Second)
    cancel()

    time.Sleep(2 * time.Second)
}

main函式中,我們使用context.WithCancel函式創建了一個新的context,并將其傳遞給worker函式,同時啟動協程運行worker函式,

worker函式執行5s后,主協程呼叫cancel函式來終止worker協程,之后,worker協程中監聽取消信號的select陳述句,將能夠捕捉到這個信號,執行終止協程操作,

最后,在退出協程時,通過defer陳述句實作資源的釋放,綜上,我們實作了協程的優雅關閉,同時也正確回收了資源,

4. 需要主動關閉協程運行的常見場景

4.1 協程在執行一個不斷重復的任務

協程在執行一個不斷重復的任務時,此時協程是不會主動終止運行的,但是在某個時刻之后,不需要再繼續執行該任務了,需要主動關閉goroutine的執行,釋放協程的資源,

這里以etcd為例來進行說明,etcd主要用于在分布式系統中存盤配置資訊、元資料和一些小規模的共享資料,也就是說,我們可以在etcd當中存盤一些鍵值對,那么,如果我們想要設定鍵值對的有效期,那該如何實作呢?

etcd中存在一個租約的概念,租約可以看作是一個時間段,該時間段內某個鍵值對的存在是有意義的,而在租約到期后,該鍵值對的存在便沒有意義,可以被洗掉,同時一個租約可以作用于多個鍵值對,下面先展示如何將一個租約和一個key進行關聯的示例:

// client 為 etcd客戶端的連接,基于此建立一個Lease實體
// Lease示例提供一些api,能過創建租約,取消租約,續約租約
lease := clientv3.NewLease(client)

// 創建一個租約,同時租約時間為10秒
grantResp, err := lease.Grant(context.Background(), 10)
if err != nil {
    log.Fatal(err)
}
// 租約ID,每一個租約都有一個唯一的ID
leaseID := grantResp.ID

// 將租約與key進行關聯,此時該key的有效期,也就是該租約的有效期
_, err = kv.Put(context.Background(), "key1", "value1", clientv3.WithLease(leaseID))
if err != nil {
    log.Fatal(err)
}

以上代碼演示了如何在etcd中創建一個租約并將其與一個鍵值對進行關聯,首先,通過etcd客戶端的連接創建了一個Lease實體,該實體提供了一些api,可以創建租約、取消租約和續約租約,然后使用Grant函式創建了一個租約并指定了租約的有效期為10秒,接下來,獲取租約ID,每個租約都有一個唯一的ID,最后,使用Put函式將租約與key進行關聯,從而將該key的有效期設定為該租約的有效期,

所以,我們如果想要操作etcd中鍵值對的有效期,只需要操作租約的有效期即可,

而剛好,etcd其實定義了一個Lease介面,該介面定義了對租約的一些操作,能過創建租約,取消租約,同時也支持續約租約,獲取過期時間等內容,具體如下:

type Lease interface {
   // 1. 創建一個新的租約
   Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error)
   // 2. 取消租約
   Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error)
   // 3. 獲取租約的剩余有效期
   TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error)
   // 4. 獲取所有的租約
   Leases(ctx context.Context) (*LeaseLeasesResponse, error)
   // 5. 不斷對租約進行續約,這里假設10s后過期,此時大概的含義為每隔10s續約一次租約,呼叫該方法后,租約將永遠不會過期  
   KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error)
   // 6. 續約一次租約
   KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error)
   // 7. 關閉Lease實體 
   Close() error
}

到此為止,我們引出了Lease介面,而其中KeepAlive方法便是我們今日的主角,從該方法定義可以看出,當呼叫KeepAlive方法對某個租約進行續約后,其每隔一段時間都會執行對目標租約的續約操作,這個時候一般都是啟動一個協程,由協程來完成對租約的續約操作,

此時協程其實就是在執行一個不斷重復的任務,那如果Lease介面的實體呼叫了Close方法,想要回收掉Lease實體,不會再通過該實體對租約進行操作,回收掉Lease所有占據的資源,那么KeepAlive方法創建的協程,此時也應該被主動關閉,不應該再繼續執行下去,

事實上,當前etcdLease介面中KeepAlive方法的默認實作也是如此,并且對主動關閉協程運行的實作,也是通過context傳遞物件,select獲取取消信號,最后通過defer 來回收資源這三者組合起來實作的,

下面來看看執行續約操作的函式,會啟動一個協程在后臺不斷執行,具體實作如下:

func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
   for {
      var tosend []LeaseID
      
      now := time.Now()
      l.mu.Lock()
      // keepAlives 是保存了所有待續約的 租約ID
      for id, ka := range l.keepAlives {
         // 然后nextKeepAlive為下次續約的時間,如果超過該時間,則執行續約操作
         if ka.nextKeepAlive.Before(now) {
            tosend = append(tosend, id)
         }
      }
      l.mu.Unlock()
      // 發送續約請求
      for _, id := range tosend {
         r := &pb.LeaseKeepAliveRequest{ID: int64(id)}
         // 向etcd集群發送續約請求
         if err := stream.Send(r); err != nil {
            return
         }
      }

      select {
      // 每隔500ms執行一次
      case <-time.After(500 * time.Millisecond):
      // 如果接收到終止信號,則直接終止
      case <-l.stopCtx.Done():
         return
      }
   }
}

可以看到,其會不斷回圈,首先會檢查當前時間是否超過了所有租約的下次續約時間,如果超過了,則會將這些租約的 ID 放入 tosend 陣列中,并在回圈的下一步中向 etcd集群發送續約請求,接著會等待 500 毫秒,然后再次執行上述操作,正常情況下,其不會退出回圈,會一直向etcd集群發送續約請求,除非收到了終止信號,其才會退出,從而正常結束協程,

stopCtx則是lessor實體的變數,用于傳遞取消信號,在創建 lessor 實體時,stopCtx 是由 context.WithCancel() 函式創建的,這個函式會回傳兩個物件:一個帶有取消方法的 context.Context 物件(即 stopCtx),以及一個函式物件 stopCancel,呼叫這個函式會取消背景關系物件,具體如下:

// 創建Lease實體
func NewLeaseFromLeaseClient(remote pb.LeaseClient, c *Client, keepAliveTimeout time.Duration) Lease {
   // ...省略一些無關內容
   reqLeaderCtx := WithRequireLeader(context.Background())
   // 通過withCancel函式創建cancelCtx物件
   l.stopCtx, l.stopCancel = context.WithCancel(reqLeaderCtx)
   return l
}

lessor.Close() 函式中,我們呼叫 stopCancel() 函式來發送取消信號,

func (l *lessor) Close() error {
   l.stopCancel()
   // close for synchronous teardown if stream goroutines never launched
   // 省略無關內容
   return nil
}

因為 sendKeepAliveLoop() 協程會在 stopCtx 上等待信號,所以一旦呼叫了 stopCancel(),協程會收到信號并退出,這個機制非常靈活,因為stopCtx是實體的成員變數,所以lessor實體創建的所有協程,都可以通過監聽stopCtx來決定是否要退出執行,

5.總結

這篇文章主要介紹了為什么需要主動關閉goroutine,以及在Go語言中關閉goroutine的常見套路,

文章首先介紹了為什么需要主動關閉goroutine,接下來,文章詳細介紹了Go語言中關閉goroutine的常見套路,包括傳遞終止信號和協程內部捕捉終止信號,在傳遞終止信號的方案中,文章介紹了如何使用context物件傳遞信號,并使用select陳述句等待信號,在協程內部捕捉終止信號的方案中,文章介紹了如何使用defer陳述句來回收資源,

最后,文章列舉了需要主動關閉協程運行的常見場景,如協程在執行一個不斷重復的任務,在不再需要繼續執行下去的話,就需要主動關閉協程的執行,希望通過本文的介紹,讀者能夠掌握如何在適當的時候關閉goroutine,從而避免資源浪費的問題,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552416.html

標籤:其他

上一篇:【pandas基礎】--資料整理

下一篇:返回列表

標籤雲
其他(159010) Python(38129) JavaScript(25421) Java(18034) C(15226) 區塊鏈(8265) C#(7972) AI(7469) 爪哇(7425) MySQL(7184) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5340) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4572) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2433) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1972) 功能(1967) Web開發(1951) HtmlCss(1936) python-3.x(1918) C++(1915) 弹簧靴(1913) xml(1889) PostgreSQL(1876) .NETCore(1860) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 如何優雅得關閉協程呢

    1.簡介 本文將介紹首先為什么需要主動關閉goroutine,并介紹如何在Go語言中關閉goroutine的常見套路,包括傳遞終止信號和協程內部捕捉終止信號。之后,文章列舉了需要主動關閉協程運行的常見場景,如啟動一個協程執行一個不斷重復的任務。希望通過本文的介紹,讀者能夠掌握如何在適當的時候關閉go ......

    uj5u.com 2023-05-15 07:24:22 more
  • 【pandas基礎】--資料整理

    pandas進行資料整理的意義在于,它是資料分析、資料科學和機器學習的前置步驟。 通過資料整理可以提前了解資料的概要,缺失值、重復值等情況,為后續的分析和建模提供更為可靠的資料基礎。 本篇主要介紹利用pandas進行資料整理的各種方法。 1. 資料概要 獲取資料概要資訊可以幫助我們了解資料的基本情況 ......

    uj5u.com 2023-05-15 07:24:17 more
  • 數字分頻器設計(偶數分頻、奇數分頻、小數分頻、半整數分頻、狀態

    偶數分頻:無論是通過D觸發器還是計數器實作,這類分頻都是最容易得到的,并且占空比容易控制在50%。對于D觸發器實作偶數分頻來說,分頻數只能得2^n,其余分頻數只能由計數器法等其他方法實作。除此以外,隨著分頻的數目不斷增大,通過D觸發器實作觸發器數目會增多,在電路設計的程序中應當考慮面積因素。對于計數... ......

    uj5u.com 2023-05-15 07:24:09 more
  • 數字分頻器設計(偶數分頻、奇數分頻、小數分頻、半整數分頻、狀態

    偶數分頻:無論是通過D觸發器還是計數器實作,這類分頻都是最容易得到的,并且占空比容易控制在50%。對于D觸發器實作偶數分頻來說,分頻數只能得2^n,其余分頻數只能由計數器法等其他方法實作。除此以外,隨著分頻的數目不斷增大,通過D觸發器實作觸發器數目會增多,在電路設計的程序中應當考慮面積因素。對于計數... ......

    uj5u.com 2023-05-15 07:22:13 more
  • R語言資料繪圖學習(0x01)-安裝ggplot2與嘗試

    0x01 安裝與R基礎 一直聽說資料分析里R語言是比較‘正統’,況且久聞ggplot2這些R語言的資料分析庫大名,想到今后資料分析和整理的需要,這里開一個簡單的系列學習一些R語言和ggplot2的繪圖基礎。本人學習的書籍是Winston Chang大佬的《R Graphics Cookbook》,且 ......

    uj5u.com 2023-05-13 07:41:55 more
  • python高級技術(行程二)

    一 行程物件及其他方法 '''一臺計算機上面運行著很多行程,那么計算機是如何區分并管理這些行程服務端的呢?計算機會給每一個運行的行程分配一個PID號如何查看 windows電腦 進入cmd輸入tasklist即可查看 tasklist|findstr PID查看具體的行程 linux電腦 進入終端之 ......

    uj5u.com 2023-05-13 07:36:35 more
  • 使用 IDEA 時突然斷電導致 git 本地分支損壞的解決方案

    使用IDEA提交專案的時候突然斷電,重啟后專案 git 損壞,所有檔案變成了 untracked,IDEA 界面上表示為所有檔案名變成綠色,并且無法 pull (也可能是無法 push) 提示 Git Pull Failed From http://***************** * branc ......

    uj5u.com 2023-05-13 07:24:33 more
  • Spring AOP 分享

    初級篇 AOP是什么? Aspect-oriented Programming (AOP) 即面向切面編程。簡單來說,AOP 是一種編程范式,允許我們模塊化地定義橫跨多個物件的行為。AOP 可以幫助我們將應用程式的關注點分離,使得代碼更加清晰、易于維護和擴展。 大白話:在方法執行前后運行指定代碼,比 ......

    uj5u.com 2023-05-13 07:22:03 more
  • 閱讀論文的方法和技巧(快速且有效)

    如何從一個小白快速開始入手看論文,然后看論文,發論文。請仔細看下面的講解。歡迎大家一起交流和補充。 閱讀論文的方法和技巧 一.閱讀論文五個重要步驟(通常用時30-60分鐘) 1.第一遍是快速瀏覽論文的摘要、結論、框架圖,有助于把握核心,對論文的內容形成整體感知。(5-10分鐘) 當然,這一遍建議在網 ......

    uj5u.com 2023-05-12 10:48:00 more
  • Java的列舉型別

    如果類的物件的數量只有有限個,并且可以確定物件的屬性,那么考慮使用列舉類。所有的列舉型別都是 Enum 類的子類。它們繼承了這個類的許多方法。 ......

    uj5u.com 2023-05-12 10:47:56 more