如何在Rust中创建单线程单体?
我目前正在尝试用rust包装一个C库,它有一些要求。C库只能在单个线程上运行,并且只能在同一线程上初始化/清理一次。我想要下面这样的东西如何在Rust中创建单线程单体?,rust,concurrency,ffi,Rust,Concurrency,Ffi,我目前正在尝试用rust包装一个C库,它有一些要求。C库只能在单个线程上运行,并且只能在同一线程上初始化/清理一次。我想要下面这样的东西 extern "C" { fn init_lib() -> *mut c_void; fn cleanup_lib(ctx: *mut c_void); } // This line doesn't work. static mut CTX: Option<(ThreadId, Rc<Context>
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
// This line doesn't work.
static mut CTX: Option<(ThreadId, Rc<Context>)> = None;
struct Context(*mut c_void);
impl Context {
fn acquire() -> Result<Rc<Context>, Error> {
// If CTX has a reference on the current thread, clone and return it.
// Otherwise initialize the library and set CTX.
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe { cleanup_lib(self.0); }
}
}
extern“C”{
fn init_lib()->*mut c_void;
fn清理库(ctx:*多个c_void);
}
//这条线不行。
静态mut CTX:选项=无;
结构上下文(*mut c_void);
impl上下文{
fn acquire()->结果{
//如果CTX在当前线程上有引用,请克隆并返回它。
//否则,初始化库并设置CTX。
}
}
上下文的impl Drop{
fn下降(&mut自我){
不安全{cleanup_lib(self.0);}
}
}
有人有一个很好的方法来实现这样的目标吗?我尝试提出的每个解决方案都涉及到创建互斥体/Arc,并使
上下文
类型发送
和同步
,这是我不希望的,因为我希望它保持单线程。我提出的一个有效解决方案就是自己实现引用计数,完全不需要Rc
#![feature(once_cell)]
use std::{error::Error, ffi::c_void, fmt, lazy::SyncLazy, sync::Mutex, thread::ThreadId};
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
#[derive(Debug)]
pub enum ContextError {
InitOnOtherThread,
}
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ContextError::InitOnOtherThread => {
write!(f, "Context already initialized on a different thread")
}
}
}
}
impl Error for ContextError {}
struct StaticPtr(*mut c_void);
unsafe impl Send for StaticPtr {}
static CTX: SyncLazy<Mutex<Option<(ThreadId, usize, StaticPtr)>>> =
SyncLazy::new(|| Mutex::new(None));
pub struct Context(*mut c_void);
impl Context {
pub fn acquire() -> Result<Context, ContextError> {
let mut ctx = CTX.lock().unwrap();
if let Some((id, ref_count, ptr)) = ctx.as_mut() {
if *id == std::thread::current().id() {
*ref_count += 1;
return Ok(Context(ptr.0));
}
Err(ContextError::InitOnOtherThread)
} else {
let ptr = unsafe { init_lib() };
*ctx = Some((std::thread::current().id(), 1, StaticPtr(ptr)));
Ok(Context(ptr))
}
}
}
impl Drop for Context {
fn drop(&mut self) {
let mut ctx = CTX.lock().unwrap();
let (_, ref_count, ptr) = ctx.as_mut().unwrap();
*ref_count -= 1;
if *ref_count == 0 {
unsafe {
cleanup_lib(ptr.0);
}
*ctx = None;
}
}
}
#![特征(一次单元)]
使用std:{error::error,ffi::c_void,fmt,lazy::SyncLazy,sync::Mutex,thread::ThreadId};
外部“C”{
fn init_lib()->*mut c_void;
fn清理库(ctx:*多个c_void);
}
#[导出(调试)]
发布枚举上下文错误{
初始化另一个线程,
}
impl fmt::ContextError的显示{
fn fmt(&self,f:&mut fmt::Formatter我认为最“粗俗”的方法是使用和一个描述库操作的枚举
此模块唯一面向公众的元素是launch_lib()
,SafeLibRef
结构(但不是其内部),以及pub fn
,它们是impl SafeLibRef
的一部分
此外,这个例子有力地代表了一种哲学,即处理全球国家的最佳方式是不拥有任何权力
我对Result::unwrap()
调用进行了快速而宽松的处理。更好地处理错误情况会更负责任
use std::sync::{ atomic::{ AtomicBool, Ordering }, mpsc::{ SyncSender, Receiver, sync_channel } };
use std::ffi::c_void;
extern "C" {
fn init_lib() -> *mut c_void;
fn do_op_1(ctx: *mut c_void, a: u16, b: u32, c: u64) -> f64;
fn do_op_2(ctx: *mut c_void, a: f64) -> bool;
fn cleanup_lib(ctx: *mut c_void);
}
enum LibOperation {
Op1(u16,u32,u64,SyncSender<f64>),
Op2(f64, SyncSender<bool>),
Terminate(SyncSender<()>),
}
#[derive(Clone)]
pub struct SafeLibRef(SyncSender<LibOperation>);
fn lib_thread(rx: Receiver<LibOperation>) {
static LIB_INITIALIZED: AtomicBool = AtomicBool::new(false);
if LIB_INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() {
panic!("Tried to double-initialize library!");
}
let libptr = unsafe { init_lib() };
loop {
let op = rx.recv();
if op.is_err() {
unsafe { cleanup_lib(libptr) };
break;
}
match op.unwrap() {
LibOperation::Op1(a,b,c,tx_res) => {
let res: f64 = unsafe { do_op_1(libptr, a, b, c) };
tx_res.send(res).unwrap();
},
LibOperation::Op2(a, tx_res) => {
let res: bool = unsafe { do_op_2(libptr, a) };
tx_res.send(res).unwrap();
}
LibOperation::Terminate(tx_res) => {
unsafe { cleanup_lib(libptr) };
tx_res.send(()).unwrap();
break;
}
}
}
}
/// This needs to be called no more than once.
/// The resulting SafeLibRef can be cloned and passed around.
pub fn launch_lib() -> SafeLibRef {
let (tx,rx) = sync_channel(0);
std::thread::spawn(|| lib_thread(rx));
SafeLibRef(tx)
}
// This is the interface that most of your code will use
impl SafeLibRef {
pub fn op_1(&self, a: u16, b: u32, c: u64) -> f64 {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op1(a, b, c, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn op_2(&self, a: f64) -> bool {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op2(a, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn terminate(&self) {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Terminate(res_tx)).unwrap();
res_rx.recv().unwrap();
}
}
使用std::sync:{AtomicBool,Ordering},mpsc:{SyncSender,Receiver,sync_channel};
使用std::ffi::c_void;
外部“C”{
fn init_lib()->*mut c_void;
fn do_op_1(ctx:*mut c_void,a:u16,b:u32,c:u64)->f64;
fn do_op_2(ctx:*mut c_void,a:f64)->bool;
fn清理库(ctx:*多个c_void);
}
枚举操作{
Op1(u16、u32、u64、同步发送器),
Op2(f64,同步发送器),
终止(同步发送方),
}
#[衍生(克隆)]
发布结构SafeLibRef(SyncSender);
fn lib_线程(接收端:接收器){
静态库已初始化:AtomicBool=AtomicBool::new(false);
if LIB_INITIALIZED.compare_交换(false、true、Ordering::SeqCst、Ordering::SeqCst)。is_err(){
惊慌失措!(“试图双重初始化库!”);
}
设libptr=unsafe{init_lib()};
环路{
设op=rx.recv();
如果操作是_err(){
不安全{cleanup_lib(libptr)};
打破
}
匹配操作展开(){
LibOperation::Op1(a、b、c、tx_res)=>{
设res:f64=unsafe{do_op_1(libptr,a,b,c)};
发送(res.unwrap();
},
LibOperation::Op2(a,tx_res)=>{
let res:bool=unsafe{do_op_2(libptr,a)};
发送(res.unwrap();
}
LibOperation::终止(tx_res)=>{
不安全{cleanup_lib(libptr)};
发送(()).unwrap();
打破
}
}
}
}
///这只需要调用一次。
///生成的SafeLibRef可以被克隆并传递。
pub fn launch_lib()->SafeLibRef{
let(tx,rx)=同步信道(0);
std::thread::spawn(| | lib|u thread(rx));
SafeLibRef(德克萨斯州)
}
//这是大多数代码将使用的接口
impl-SafeLibRef{
发布fn op_1(&self,a:u16,b:u32,c:u64)->f64{
let(res_tx,res_rx)=同步信道(1);
self.0.send(LibOperation::Op1(a,b,c,res_-tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn op_2(&self,a:f64)->bool{
let(res_tx,res_rx)=同步信道(1);
self.0.send(LibOperation::Op2(a,res_tx)).unwrap();
res_rx.recv().unwrap()
}
发布fn终止(&self){
let(res_tx,res_rx)=同步信道(1);
self.0.send(LibOperation::Terminate(res_tx)).unwrap();
res_rx.recv().展开();
}
}
您的代码确保只能在特殊线程中获取上下文,但不会阻止它被重新使用(在其他线程中重复调用)。既然互斥锁被锁定,那么一旦通过acquire()创建上下文,这不会触发错误情况吗
,调用者可以用它做任何事情,包括将它传递给其他线程。由于上下文
持有原始指针,它既是!Sync
又是!Send
,因此不能在多个线程上使用。