與會話型別的跨執行緒通訊

會話型別是一種告訴編譯器你希望用於線上程之間進行通訊的協議的方法 - 不是 HTTP 或 FTP 中的協議,而是執行緒之間的資訊流模式。這很有用,因為編譯器現在會阻止你意外破壞協議並導致執行緒之間的死鎖或活鎖 - 一些最難以除錯的問題,以及 Heisenbugs 的主要來源。會話型別與上述通道的工作方式類似,但開始使用時可能會更加令人生畏。這是一個簡單的雙執行緒通訊:

// Session Types aren't part of the standard library, but are part of this crate.
// You'll need to add session_types to your Cargo.toml file.
extern crate session_types;

// For now, it's easiest to just import everything from the library.
use session_types::*;

// First, we describe what our client thread will do. Note that there's no reason
// you have to use a client/server model - it's just convenient for this example.
// This type says that a client will first send a u32, then quit. `Eps` is
// shorthand for "end communication".
// Session Types use two generic parameters to describe the protocol - the first
// for the current communication, and the second for what will happen next.
type Client = Send<u32, Eps>;
// Now, we define what the server will do: it will receive as u32, then quit.
type Server = Recv<u32, Eps>;

// This function is ordinary code to run the client. Notice that it takes    
// ownership of a channel, just like other forms of interthread communication - 
// but this one about the protocol we just defined.
fn run_client(channel: Chan<(), Client>) {
    let channel = channel.send(42);
    println!("The client just sent the number 42!");
    channel.close();
}

// Now we define some code to run the server. It just accepts a value and prints
// it.
fn run_server(channel: Chan<(), Server>) {
    let (channel, data) = channel.recv();
    println!("The server received some data: {}", data);
    channel.close();
}

fn main() {
    // First, create the channels used for the two threads to talk to each other.
    let (server_channel, client_channel) = session_channel();

    // Start the server on a new thread
    let server_thread = std::thread::spawn(move || {
        run_server(server_channel);
    });

    // Run the client on this thread.
    run_client(client_channel);

    // Wait for the server to finish.
    server_thread.join().unwrap();
}

你應該注意到,如果將伺服器移動到其自己的函式,則 main 方法看起來與上面定義的跨執行緒通訊的主方法非常相似。如果你要執行它,你會得到輸出:

The client just sent the number 42!
The server received some data: 42

以該順序。

為什麼要經歷定義客戶端和伺服器型別的所有麻煩?為什麼我們要重新定義客戶端和伺服器中的頻道?這些問題有相同的答案:編譯器將阻止我們破壞協議! 如果客戶端試圖接收資料而不是傳送它(這會導致普通程式碼死鎖),程式將無法編譯,因為客戶端的通道物件*上沒有 recv 方法。*此外,如果我們嘗試以可能導致死鎖的方式定義協議(例如,如果客戶端和伺服器都嘗試接收值),那麼在建立通道時編譯將失敗。這是因為 SendRecv雙重型別,這意味著如果伺服器執行了一個,則客戶端必須執行另一個 - 如果兩者都試圖通過 Recv,那麼你將遇到麻煩。Eps 是它自己的雙重型別,因為它同意客戶端和伺服器同意關閉通道。

當然,當我們在通道上進行一些操作時,我們會轉到協議中的新狀態,並且我們可用的功能可能會改變 - 所以我們必須重新定義通道繫結。幸運的是,session_types 為我們處理這個問題,並且總是返回新的頻道(close 除外,在這種情況下沒有新的頻道)。這也意味著通道上的所有方法也會獲得通道的所有權 - 因此,如果你忘記重新定義通道,編譯器也會給你一個錯誤。如果你刪除一個通道而沒有關閉它,那也是一個執行時錯誤(不幸的是,在編譯時無法檢查)。

除了 SendRecv 之外,還有更多型別的通訊 - 例如,Offer 為通道的另一端提供了在協議的兩個可能分支之間進行選擇的能力,RecVar 協同工作以允許協議中的迴圈和遞迴。session_types GitHub 儲存庫中提供了更多會話型別和其他型別的示例。庫的文件可以在這裡找到