簡體   English   中英

從 Rust 中的回調訪問全局狀態

[英]Accessing global state from a callback in rust

我正在學習 rust,我一直在嘗試重寫我在 C# 中用 rust 完成的項目,但在嘗試從回調訪問全局狀態時遇到了困難,

有沒有一種簡單的方法可以在 Rust 中做到這一點? 請記住,我無法向回調添加新參數。

例如:

use std::collections::HashMap;
use std::time::instant;
use lib::bar;

struct Struct2{
    foo: bar,
    word: String,
}

struct GlobalState{
    running: bool,
    now: Instant,
    map: HashMap<String, Struct2>,
    hook_id: usize,
    current_id: String,
}

impl GlobalState{
    fn init() -> Self{
        let hook_id = unsafe {set_ext_hook(HOOK_ID, system_hook)};
        // Omitted initialization code.
    }
    // Omitted state mutation functions.
}

unsafe extern "system" fn system_hook(ext_param1:usize, ext_param2: usize) -> isize {
    // use global state here
}

我嘗試使用諸如lazy_static 和once_cell 之類的板條箱,但它們不起作用,因為我使用的外部結構(在本例中為lib::bar) “無法在線程之間安全地發送”

到目前為止,我的代碼是單線程的(我計划在實現它時為程序的 gui 使用不同的線程)

任何幫助表示贊賞,謝謝。

您似乎正在處理既不是Send也不是Sync ,因此 Rust 不允許您將其放置在全局中,即使是在互斥鎖中。 從問題中不清楚這是否是lib::bar真正線程不安全的結果,或者只是它在后台使用原始指針的意外后果。 還不清楚您是否可以修改lib::bar以使其類型為SendSync

最保守地假設lib::bar不能更改,並考慮到您的程序是單線程的,您唯一安全的選擇是創建線程本地狀態:

use std::cell::RefCell;
use std::thread_local;

struct Foo(*const i32); // a non-Send/Sync type

struct GlobalState {
    foo: Foo,
    data: String,
    mutable_data: RefCell<String>,
}

thread_local! {
    static STATE: GlobalState = GlobalState {
        foo: Foo(std::ptr::null()),
        data: "bla".to_string(),
        mutable_data: RefCell::new("".to_string()),
    };
}

您可以從任何函數訪問該狀態(並修改其內部可變部分):

fn main() {
    STATE.with(|state| {
        assert_eq!(state.foo.0, std::ptr::null());
        assert_eq!(state.data, "bla");
        assert_eq!(state.mutable_data.borrow().as_str(), "");
        state.mutable_data.borrow_mut().push_str("xyzzy");
    });
    STATE.with(|state| {
        assert_eq!(state.mutable_data.borrow().as_str(), "xyzzy");
    });
}

操場

請注意,如果您嘗試從不同的線程訪問“全局”狀態,每個線程都會獲得自己的狀態副本:

fn main() {
    STATE.with(|state| {
        state.mutable_data.borrow_mut().push_str("xyzzy");
    });
    std::thread::spawn(|| {
        STATE.with(|state| {
            // change to "xyzzy" happened on the other copy
            assert_eq!(state.mutable_data.borrow().as_str(), "");
        })
    })
    .join()
    .unwrap();
}

操場

一種選擇是變量的“線程限制”。 這意味着對變量的所有訪問都發生在一個線程上。 通常,您為此創建一個專用線程,並為您的變量創建一個代理,該代理在其他線程之間共享,並負責從限制線程獲取消息和從限制線程獲取消息。

在 Rust 中,這種線程間通信通常是使用通道完成的。 我將展示您的代碼的簡化版本 - 其中lib::bar只是包裝了一個 i32 指針。 指針不實現發送+同步,是您 API 的一個很好的替代品。

代碼相當冗長,我作弊並且沒有對所有sendrecv調用實現錯誤處理,你絕對應該這樣做。 盡管冗長,但添加新功能非常簡單——它主要包括向 Message 和 Reply 枚舉添加一個變體,並復制現有功能。

use lazy_static::lazy_static;
use std::sync::mpsc::sync_channel;

pub mod lib {
    pub struct Bar(*mut i32);
    impl Bar {
        pub fn new() -> Self {
            Bar(Box::into_raw(Box::new(0)))
        }
        pub fn set(&mut self, v: i32) {
            unsafe { *self.0 = v };
        }
        pub fn get(&self) -> i32 {
            unsafe { *self.0 }
        }
    }
}

enum Message {
    Set(i32),
    Get,
    Shutdown,
}

enum Reply {
    Set,
    Get(i32),
    Shutdown,
}

fn confinement_thread(
    receiver: std::sync::mpsc::Receiver<(Message, std::sync::mpsc::SyncSender<Reply>)>,
) {
    // Create the confined state
    let mut bar = lib::Bar::new();

    // Handle messages and forward them
    loop {
        let (mesg, reply_channel) = receiver.recv().unwrap();
        match mesg {
            Message::Set(v) => {
                eprintln!("    worker: setting value to {}", v);
                bar.set(v);
                reply_channel.send(Reply::Set).unwrap();
            }
            Message::Get => {
                let v = bar.get();
                eprintln!("    worker: getting value = {}", v);
                reply_channel.send(Reply::Get(v)).unwrap();
            }
            Message::Shutdown => {
                eprintln!("    worker: shutting down");
                reply_channel.send(Reply::Shutdown).unwrap();
                break;
            }
        }
    }
}

// This can be cloned happily
// and supports Send+Sync
struct GlobalProxy {
    channel: std::sync::mpsc::SyncSender<(Message, std::sync::mpsc::SyncSender<Reply>)>,
}

impl GlobalProxy {
    pub fn set(&self, v: i32) {
        eprintln!("  proxy: setting value to {}", v);
        let (a, b) = sync_channel(0);
        self.channel.send((Message::Set(v), a)).unwrap();
        let m = b.recv().unwrap();
        assert!(matches!(m, Reply::Set));
    }

    pub fn get(&self) -> i32 {
        eprintln!("  proxy: getting value");
        let (a, b) = sync_channel(0);
        self.channel.send((Message::Get, a)).unwrap();
        let m = b.recv().unwrap();
        if let Reply::Get(v) = m {
            eprintln!("  proxy: got value={}", v);
            v
        } else {
            unreachable!();
        }
    }

    pub fn die(&self) {
        eprintln!("Telling worker thread to shut down");
        let (a, b) = sync_channel(0);
        self.channel.send((Message::Shutdown, a)).unwrap();
        let m = b.recv().unwrap();
        assert!(matches!(m, Reply::Shutdown));
    }
}

lazy_static! {
    static ref G: GlobalProxy = {
        // Create com channels
        let (to_global, from_world) = sync_channel(0);
        // Keep one end for the proxy,
        let global = GlobalProxy{ channel: to_global};
        // The other goes to the worker thread
        std::thread::spawn(|| {confinement_thread(from_world)});
        global
    };
}

pub fn main() {
    eprintln!("global.get() = {}", G.get());
    eprintln!("global.set(10)",);
    G.set(10);
    eprintln!("global.get() = {}", G.get());

    G.die()
}

工作版本

可能有很多機會使用宏來減少冗長,但我發現這個版本更有指導意義。

另一項改進是將回復通道放入消息對象中——這將允許我們刪除回復枚舉。

在某些情況下,通過將一個函數傳遞給限制線程來運行,而不是一條消息,可以移除 Message 對象。 就像是:

impl GlobalProxy { 
  fn run_confined(&self f: dyn Fn(&lib::Bar) + Send + Sync)
   {...}
}

但是以一種很好的方式處理帶有返回值的函數是很棘手的。

我的例子不是從全局狀態,而是從外部范圍訪問變量。 也許它會有用。

use std::collections::HashMap;

fn main() {
    let a = String::from("Hello world");
    let b = String::from("Another world");
    let mut keys: HashMap<String, String> = HashMap::new();

    let callback = |line: String| {
        keys.insert(line.to_string(), line.to_string());
        println!("{}", b);
        println!("{}", line);
        println!("{:?}", keys);
    };

    compute(a, callback)
}

fn compute<F>(a: String, mut f: F)
    where F: FnMut(String)
{
    f(a)
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM