真實世界的 Go 設計模式 - 資源獲取即初始化

RAII,全稱資源獲取即初始化(英語:Resource Acquisition IInitialization),它是在一些面嚮對象語言中的一種慣用法。RAII 源於 C++,在 Java,C#,D,Ada,Vala 和 Rust 中也有應用。1984-1989 年期間,比雅尼 · 斯特勞斯特魯普和安德魯 · 柯尼希在設計 C++ 異常時,爲解決資源管理時的異常安全性而使用了該用法,後來比雅尼 · 斯特勞斯特魯普將其稱爲 RAII。

主要思想是:

  1. 在構造函數中申請資源 (acquire resource)

  2. 在析構函數中釋放資源 (release resource)

這樣就可以把資源的獲取和釋放跟對象的生命週期綁定在一起。

RAII 的主要優點:

RAII 的主要作用是在不失代碼簡潔性的同時,可以很好地保證代碼的異常安全性。

下面的 C++ 實例說明了如何用 RAII 訪問文件和互斥量:

#include <string>
#include <mutex>
#include <iostream>
#include <fstream>
#include <stdexcept>

void write_to_file(const std::string & message)
{
    // 創建關於文件的互斥鎖
    static std::mutex mutex;

    // 在訪問文件前進行加鎖
    std::lock_guard<std::mutex> lock(mutex);

    // 嘗試打開文件
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");

    // 輸出文件內容
    file << message << std::endl;

    // 當離開作用域時,文件句柄會被首先析構 (不管是否拋出了異常)
    // 互斥鎖也會被析構 (同樣地,不管是否拋出了異常)
}

比如 Rust 中,m.lock()獲取到鎖,也不需要顯示的釋放鎖:

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

    println!("m = {:?}", m);
}

Go 語言倒沒有沒有 C++ 那樣的構造函數和析構函數的概念, 但是 Go 中併發原語也算有半個類似 RAII 的技術。

我們知道,Go 標準庫的的併發原語都不需要特別的初始化,我們直接使用它的零值即可,比如:

var lock sync.Mutex
var rwLock sync.RWMutex
var wg sync.WaitGroup
......

但是 Go 並沒有實現自動化的析構函數,對鎖自動釋放,我們一般使用defer來完成這一步:

lock.Lock()
defer lock.Unlock()

rwlock.RLock()
defer rwlock.RUnlock()

wg.Done()
......
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MiACdPNOhnXRoysRX2VBVA