1

我当前的项目需要记录线程执行期间发生的各种事件的一些信息。这些事件由线程 id 保存在全局结构索引中:

RECORDER1: HashMap<ThreadId, Vec<Entry>> = HashMap::new();

每个线程都将 new 附加Entry到其向量中。因此,线程访问“不相交”的向量。Rust 需要同步原语来完成上述工作。所以真正的实现看起来像:

struct Entry {
    // ... not important.
}

#[derive(Clone, Eq, PartialEq, Hash)]
struct ThreadId;

// lazy_static necessary to initialize this data structure.
lazy_static! {
    /// Global data structure. Threads access disjoint entries based on their unique thread id.
    /// "Outer" mutex necessary as lazy_static requires sync (so cannot use RefCell).
    static ref RECORDER2: Mutex<HashMap<ThreadId, Vec<Entry>>> = Mutex::new(HashMap::new());
}

这可行,但所有线程都在同一个全局锁上竞争。如果一个线程可以在线程的生命周期内“借用”其各自的向量,那就太好了,这样它就可以写入它需要的所有条目,而无需每次都锁定(我知道外部锁定对于确保线程不插入是必要的同时进入 HashMap)。

为此,我们可以通过 Mutex 为 HashMap 中的值添加 Arc 和更多内部可变性:

lazy_static! {
    static ref RECORDER: Mutex<HashMap<ThreadId, Arc<Mutex<Vec<Entry>>>>> = Mutex::new(HashMap::new());
}

现在我们可以在产生线程时“检查”我们的条目:

fn local_borrow() {
    std::thread::spawn(|| {
        let mut recorder = RECORDER.lock().expect("Unable to acquire outer mutex lock.");
        let my_thread_id: ThreadId = ThreadId {}; // Get thread id...

        // Insert entry in hashmap for our thread.
        // Omit logic to check if key-value pair already existed (it shouldn't).
        recorder.insert(my_thread_id.clone(), Arc::new(Mutex::new(Vec::new())));

        // Get "reference" to vector
        let local_entries: Arc<Mutex<Vec<Entry>>> = recorder
            .get(&my_thread_id)
            .unwrap() // We just inserted this entry, so unwrap.
            .clone();  // Clone on the Arc to acquire a "copy".

        // Lock once, use multiple times.
        let mut local_entries: MutexGuard<_> = local_entries.lock().unwrap();
        local_entries.push(Entry {});
        local_entries.push(Entry {});

    });
}

这有效,这就是我想要的。但是,由于 API 限制,我必须从代​​码中不同的地方访问 MutexGuard,而无法将 MutexGuard 作为参数传递给函数。因此,我使用线程局部变量:

thread_local! {
    /// This variable is initialized lazily. Due to API constraints, we use this thread_local! to
    /// "pass" LOCAL_ENTRIES around.
    static LOCAL_ENTRIES: Arc<Mutex<Vec<Entry>>> = {
        let mut recorder = RECORDER.lock().expect("Unable to acquire outer mutex lock.");
        let my_thread_id: ThreadId = ThreadId {}; // Get thread id...

         // Omit logic to check if key-value pair already existed (it shouldn't).
        recorder.insert(my_thread_id.clone(), Arc::new(Mutex::new(Vec::new())));
        // Get "reference" to vector

        recorder
        .get(&my_thread_id)
        .unwrap() // We just inserted this entry, so unwrap.
        .clone()  // Clone on the Arc to acquire a "copy".
    }
}

我做不到,LOCAL_ENTRIES: MutexGuard<_>因为thread_local!需要'static一生。所以目前我.lock()每次想访问线程局部变量时都必须:

fn main() {
    std::thread::spawn(|| {
        // Record important message.
        LOCAL_ENTRIES.with(|entries| {
            // We have to lock every time we want to write to LOCAL_ENTRIES. It would be nice
            // to lock once and hold on to the MutexGuard for the lifetime of the thread, but
            // this is not possible to due the lifetime on the MutextGuard.
            let mut entries = entries.lock().expect("Unable to acquire lock");
            entries.push(Entry {});
        });
    });
}

很抱歉所有的代码和解释,但我真的被困住了,想说明为什么它不起作用以及我想要做什么。如何在 Rust 中解决这个问题?

还是我被互斥锁的成本挂断了?对于 any Arc<Mutex<Vec<Entry>>>,锁将始终被解锁,因此进行原子锁定的成本会很小?

感谢您的任何想法。这是Rust Playground中的完整示例。

4

0 回答 0