< 返回版块

kipade 发表于 2025-05-01 06:29

Tags:所有权转移;借用;线程;共享

现在我有一个C的SDK库,作为习作,我希望在这个C库之上包装一个Rust库给外部,在C库中,有一个typedef void* device_t类型,有对应的device_open,device_close,device_read,device_write之类的接口,我在Rust中包装了新的结构,

struct Device {
    dev: device_t, 
    rw_lock: Arc::Mutex<u32>,     
    rd_th:Arc<Mutex<Option<std::thread::JoinHandle<i32>>>>
},
struct SafePointer(*mut c_void);
unsafe impl Send for SafePointer {}

我在其实现中写了个receive函数如下:

impl Device {
pub fn start_receive(&mut self, can_cbk: NewDataCbk) -> i32 {
        if !self.dev.is_null() { //监控,不互斥
            if let Ok(_) = self.rw_lock.try_lock() {
                match self.rd_th.lock() {
                    Ok(mut th) => {
                        let th1 = &mut *th;
                        match th1 {
                            Some(th) => {
                                if !th.is_finished() {
                                    return -16;
                                }
                                //线程已经结束
                            },
                            _ => {/*线程尚未创建*/ }
                        }
                        /* 无脑地转换一下类型也绕不过去
                        let v : *mut u8 = unsafe { std::mem::transmute(self.dev) };
                        let p : device_t = unsafe { std::mem::transmute(v)  };
                        */
                        // 使用示例
                        let wrapped_ptr = SafePointer(std::ptr::null_mut());
                        //创建线程
                        *th1 = Some(thread::spawn(move||{
                            let mut buf = [0u8;1024];
                            //self.read(buf.as_mut_ptr(), buf.len(), 10);
                            let mut inner = wrapped_ptr.0;
                            let _ret = unsafe {
                                device_read(std::ptr::null_mut(), buf.as_mut_ptr(), buf.len(), 100);
                            };
                            return 0i32;
                        }));
                    },
                    Err(_) => { return -16;}
                }
            }
        }
        return -22;
    }

其中,那个SafePointer是因为我开始直接使用的时候没能找到解决办法所以问DeepSeek他告诉我的这么包装方法,但是,用了这个方法也一样不好用,始终在线程处报错^ *mut c_void cannot be shared between threads safely,照我现在的代码,我是要实现只有一个工作线程,问题肯定是出现在dev上,这个线程创建之后这个dev还仍然有效,但是我可以保证不操作了,大概编译器不知道这个事实,请问,我该如何绕过编译器这个死结?

评论区

写评论
作者 kipade 2025-05-03 20:50

谢谢,这个解决方案真的很神奇。我开始学习Rust一个月了,我对所有权转移和生命周期掌握得还是有很多欠缺


实际上你应该这么使用:

```rust
let wrapped_ptr = wrapped_ptr;  // SafePointer是Send的
let mut inner = wrapped_ptr.0;  // 指针已经移动至闭包内,可以任意使用
xiaoyaou 2025-05-02 14:15

刚好自己又写了个闭包测试,形如wrapped_ptr.0这种的解构目前编译器称为partially move部分移动。假如SafePointer是其他更复杂的类型,部分移动可以让闭包和外部分别使用不重合的两部分字段,比如:

fn main() {
    let a = A {arc: Arc::new(0), b: 0};
    std::thread::spawn(|| {
        let arc = a.arc;// 只有arc字段被move
    });
    println!("{:?}", a.b);// 依旧可以使用other字段,如果尝试使用arc,会提示"partially moved"
}
struct A {
    arc: Arc<u64>,
    other: u8,
}

--
👇
xiaoyaou:

let mut inner = wrapped_ptr.0;  // *mut c_void不是Send的,所以不能编译

xiaoyaou 2025-05-02 14:00

重新研究了一下,问题发生在这一行代码:

let mut inner = wrapped_ptr.0;  // *mut c_void不是Send的,所以不能编译

实际上你应该这么使用:

let wrapped_ptr = wrapped_ptr;  // SafePointer是Send的
let mut inner = wrapped_ptr.0;  // 指针已经移动至闭包内,可以任意使用

因为:

wrapped_ptr.0这地方,会发生模式匹配解构,你原来的写法相当于只在闭包内使用SafePointer*mut c_void类型的字段,线程闭包只会捕获使用的字段,但是字段类型*mut c_void!Send的,所以会有编译器的提示。

关于显式move

这个在你的代码示例中其实可有可无了,因为编译器会根据let wrapped_ptr = wrapped_ptr;自动推导出所有权的转移,不过显式加上我个人觉得可读性更好(当然是在没有影响其他变量的情况下)

作者 kipade 2025-05-02 10:08

抱歉,昨天一早发完贴就出门了,没来得及搞格式,我刚才调整了一下,现在清晰了。 这里我新创建一个变量wrapped_ptr,用空指针,即便为这个类型实现了Send和Sync也无济于事

--
👇
xiaoyaou:

我是要实现只有一个工作线程,问题肯定是出现在dev上,这个线程创建之后这个dev还仍然有效,但是我可以保证不操作了

按你的需求,只在单线程工作,那你要的应该是使用move关键字把你说的dev的所有权转移到线程闭包里,才是最根本的操作。 但是你贴出来的代码乱糟糟的,我尝试格式复原也看不懂你具体需求和问题在哪,按照你给定编译器提示信息,我猜你需要的是,把*mut c_coid类型(或其包装类型)的所有权移动到线程内,而不是在线程里共享其引用

作者 kipade 2025-05-02 10:05

我试了,无济于事

struct SafePointer(*mut c_void);

unsafe impl Send for SafePointer {}
unsafe impl Sync for SafePointer {}

--
👇
Bai-Jinlin: 虽然你这问题大概加上一句unsafe impl Sync for SafePointer {}就可以解决,但是看了你的代码我觉得你可能控制不好这种东西。

xiaoyaou 2025-05-01 19:24

我是要实现只有一个工作线程,问题肯定是出现在dev上,这个线程创建之后这个dev还仍然有效,但是我可以保证不操作了

按你的需求,只在单线程工作,那你要的应该是使用move关键字把你说的dev的所有权转移到线程闭包里,才是最根本的操作。 但是你贴出来的代码乱糟糟的,我尝试格式复原也看不懂你具体需求和问题在哪,按照你给定编译器提示信息,我猜你需要的是,把*mut c_coid类型(或其包装类型)的所有权移动到线程内,而不是在线程里共享其引用

Bai-Jinlin 2025-05-01 08:48

虽然你这问题大概加上一句unsafe impl Sync for SafePointer {}就可以解决,但是看了你的代码我觉得你可能控制不好这种东西。

1 共 7 条评论, 1 页