Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/rust/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在Rust中创建单线程单体?_Rust_Concurrency_Ffi - Fatal编程技术网

如何在Rust中创建单线程单体?

如何在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>

我目前正在尝试用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>)> = 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
,因此不能在多个线程上使用。