我当前的项目需要记录线程执行期间发生的各种事件的一些信息。这些事件由线程 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中的完整示例。