我有一个场景,Rust将C调用malloc一个缓冲区并将结果指针存入结构.稍后,struct将被移动到一个线程并传递给一个C函数,该函数会使它变异.
我问题的天真方法看起来像这样(游乐场):
extern crate libc;
use libc::{c_void, malloc, size_t};
use std::thread;
const INITIAL_CAPACITY: size_t = 8;
extern "C" {
fn mutate(s: *mut Storage);
}
#[repr(C)]
struct Storage {
#[allow(dead_code)]
buf: *mut c_void,
capacity: usize,
}
fn main() {
let buf = unsafe { malloc(INITIAL_CAPACITY) };
let mut s = Storage {
buf: buf,
capacity: INITIAL_CAPACITY,
};
thread::spawn(move || {
unsafe {
mutate(&mut s); // mutates s.val, maybe reallocates it, updating s.capacity if so.
}
}).join()
.unwrap();
}
Run Code Online (Sandbox Code Playgroud)
得到:
error[E0277]: the trait bound `*mut libc::c_void: std::marker::Send` is not satisfied in `[closure@src/main.rs:26:19: 30:6 s:Storage]`
--> src/main.rs:26:5
|
26 | thread::spawn(move || {
| ^^^^^^^^^^^^^ `*mut libc::c_void` cannot be sent between threads safely
|
= help: within `[closure@src/main.rs:26:19: 30:6 s:Storage]`, the trait `std::marker::Send` is not implemented for `*mut libc::c_void`
= note: required because it appears within the type `Storage`
= note: required because it appears within the type `[closure@src/main.rs:26:19: 30:6 s:Storage]`
= note: required by `std::thread::spawn`
Run Code Online (Sandbox Code Playgroud)
这是编译器的说法,因为a *mut c_void没有实现Send,也没有Storage这样做,你不能将它移动到线程闭包中.
我认为使用Unique指针可能会解决这个问题.我们试试吧(游乐场):
#![feature(ptr_internals)]
extern crate libc;
use libc::{c_void, malloc, size_t};
use std::ptr::Unique;
use std::thread;
const INITIAL_CAPACITY: size_t = 8;
extern "C" {
fn mutate(s: *mut Storage);
}
#[repr(C)]
struct Storage {
#[allow(dead_code)]
buf: Unique<c_void>,
capacity: usize,
}
fn main() {
let buf = Unique::new(unsafe { malloc(INITIAL_CAPACITY) }).unwrap();
let mut s = Storage {
buf: buf,
capacity: INITIAL_CAPACITY,
};
thread::spawn(move || {
unsafe {
mutate(&mut s); // mutates s.val, maybe reallocates it, updating s.capacity if so.
}
}).join()
.unwrap();
}
Run Code Online (Sandbox Code Playgroud)
但这给了:
warning: `extern` block uses type `std::ptr::Unique<libc::c_void>` which is not FFI-safe: this struct has unspecified layout
--> src/main.rs:11:18
|
11 | fn mutate(s: *mut Storage);
| ^^^^^^^^^^^^
|
= note: #[warn(improper_ctypes)] on by default
= help: consider adding a #[repr(C)] or #[repr(transparent)] attribute to this struct
Run Code Online (Sandbox Code Playgroud)
有没有办法让Storage结构实现Send并具有可变的指向其实例的FFI安全吗?
默认情况下,Rust 假设*mut T在线程之间发送是不安全的,这意味着包含它的结构也不安全。
你可以告诉 Rust 它确实是安全的:
unsafe impl Send for Storage {}
Run Code Online (Sandbox Code Playgroud)
它完全依赖于您对 C 如何使用此指针后面的数据的了解。实现Send意味着在使用此指针后面的对象时,C 不会依赖线程本地存储或线程特定的锁(矛盾的是,这对于大多数“线程不安全”的 C 代码来说都是如此)。
它不需要 C 来同时处理来自多个线程的访问——这就是Sync它的用途。