Cell<Buffer<T>> cannot be shared between threads safely
主线位置:一个任务的起点,
Worker和Stealer的分工。为什么Worker只能有一个主人,见 Chase-Lev 双端队列。
flowchart TB w["Worker<T>:Send,!Sync"] --> own["只有主人写 back,push / pop 不用 CAS"] w -->|"w.stealer()"| s["Stealer<T>:Send + Sync,可克隆"] s --> front["只 CAS front"] inj["Injector<T>:Sync"] --> multi["多个线程都能 push"] arc["Arc<Worker<T>> 跨线程"] --> err["E0277:Cell<Buffer<T>> cannot be shared"]
现象
想让几个线程都往同一个 deque 里 push:
let w = Arc::new(Worker::<u32>::new_lifo());
let w2 = Arc::clone(&w);
thread::spawn(move || {
w2.push(1);
})
.join()
.unwrap();
w.pop();
实测
exp-crossbeam/worker-not-sync/(crossbeam-deque 0.8.8,rustc 1.99.1,cargo build --features broken;本机路径已去掉)。三个错误,第一个是:
error[E0277]: `Cell<crossbeam_deque::deque::Buffer<u32>>` cannot be shared between threads safely
--> src/main.rs:10:17
|
10 | thread::spawn(move || {
| ___-------------_^
| | |
| | required by a bound introduced by this call
11 | | w2.push(1);
12 | | })
| |___^ `Cell<crossbeam_deque::deque::Buffer<u32>>` cannot be shared between threads safely
|
= help: within `Worker<u32>`, the trait `Sync` is not implemented for `Cell<crossbeam_deque::deque::Buffer<u32>>`
= note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock`
note: required because it appears within the type `Worker<u32>`
--> .../crossbeam-deque-0.8.8/src/deque.rs:215:12
|
215 | pub struct Worker<T> {
| ^^^^^^
= note: required for `Arc<Worker<u32>>` to implement `Send`
note: required because it's used within this closure
另外两个的标题是:
error[E0277]: `*mut ()` cannot be shared between threads safely
error[E0277]: `*mut u32` cannot be shared between threads safely
error: could not compile `worker-not-sync` (bin "worker-not-sync") due to 3 previous errors
报错里没有出现 Worker 不是 Sync 这几个字:编译器报的是 Worker 的字段。Arc<T> 要 Send,需要 T: Send + Sync;Worker 里有三个字段不是 Sync,各报一次。
从哪来
Worker 的定义:
pub struct Worker<T> {
/// A reference to the inner representation of the queue.
inner: Arc<CachePadded<Inner<T>>>,
/// A copy of `inner.buffer` for quick access.
buffer: Cell<Buffer<T>>,
/// The flavor of the queue.
flavor: Flavor,
/// Indicates that the worker cannot be shared among threads.
_marker: PhantomData<*mut ()>, // !Send + !Sync
}
unsafe impl<T: Send> Send for Worker<T> {}
buffer: Cell<Buffer<T>>:主人自己缓存一份 buffer 指针,不走原子;Cell不是Sync。Buffer<T>里有*mut T,这是第三个错误里的*mut u32。_marker: PhantomData<*mut ()>:显式地让它既不Send也不Sync,再用unsafe impl Send把Send加回来。结果是Send不Sync。
这是算法的要求,不是实现上的偷懒:Chase-Lev 双端队列的 back 只有主人一个写者,push 和 pop 写 back 时不用 CAS(Chase-Lev 双端队列)。两个线程同时 push,会写同一个槽、互相覆盖 back。!Sync 让编译器挡住这种用法。Stealer 只碰 front,用 CAS,所以是 Send + Sync、可以克隆。
怎么改
同一个程序里的三种能编译的写法,输出都是 sum=45:
// 1. Worker 留在本线程,别的线程拿 Stealer
let w = Worker::<u32>::new_lifo();
let s = w.stealer();
let h = thread::spawn(move || {
let mut sum = 0;
loop {
match s.steal() {
Steal::Success(v) => sum += v,
Steal::Retry => continue,
Steal::Empty => break sum,
}
}
});
// 2. Worker 是 Send:整个 move 进要当主人的线程
let w = Worker::<u32>::new_fifo();
thread::spawn(move || {
for i in 0..10 {
w.push(i);
}
// ...
});
// 3. 能编译,但主人被串行化了
let w = Arc::new(Mutex::new(Worker::<u32>::new_lifo()));
- 「多个线程都要往里放」:那是
Injector的用途,它是Sync的全局 FIFO 队列,任何线程都能 push、steal。rayon 的结构就是「每个工作线程一个Worker+ 一个全局Injector+ 互相持有Stealer」。 - 「一个线程放、多个线程取」:
Worker+ 若干Stealer(第 1 种)。 Arc<Mutex<Worker>>:能编译、能用,但每次 push / pop 都要拿锁,Chase-Lev「主人不用 CAS」的好处没了,不如直接用Injector或一个 channel。
小结
Worker<T>是Send不是Sync;放进Arc跨线程,报错的是它的字段:Cell<Buffer<T>>、*mut ()、*mut T。- 原因是 Chase-Lev 只允许一个主人写
back。 - 本线程留
Worker、别人拿Stealer;多个生产者用Injector。
💬 关联讨论
发起讨论加载中...