Rust 中的共享循环引用 [英] Shared circular references in Rust

查看:90
本文介绍了Rust 中的共享循环引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现一个非常幼稚的线程池模型.目前线程池的职责是:

  • 创建一个新线程并返回对它的引用
  • 维护已创建线程的映射
  • 根据线程触发的不同事件改变线程

我的主要问题是,上面的要求迫使我让线程池保留一个 HashMap 线程,但还要在创建线程时提供对线程的引用.>

最重要的是,我需要能够从任何有效地最终改变 1 个或多个线程(调用者和目标)的线程内部调用线程池的方法.

这是一个非功能实现:

使用 std::collections::HashMap;使用 std::cell::RefCell;类型 ID = u32;type ThreadPoolRef = RefCell;#[派生(调试)]结构线程池{池:HashMap,id_count: 身份证}实现线程池{fn new() ->线程池{线程池{池:HashMap::new(),id_count: 1}}fn create(&mut self) ->&RefCell<Thread>{让线程:RefCell=RefCell::new(线程::新(self.id_count, RefCell::new(self)));self.id_count = self.id_count + 1;self.pool.insert(self.id_count, thread);self.pool.get(&self.id_count).unwrap()}}#[派生(调试,克隆)]结构线程{身份证:身份证,池:线程池引用}实现线程{fn new(id: Id, pool: ThreadPoolRef) ->线 {线 {身份证:身份证,池:池}}}fn 主(){让 thread_pool = ThreadPool::new();让 thread1 = thread_pool.create();让 thread2 = thread_pool.create();//最终目标是调用thread1.method()//改变 thread1 并调用 thread_pool.method2()//这将调用有效的 thread2.method3()//改变线程 2}

Rust Play

我尝试了几种方法,例如使用 RefCell,但我开始遇到很多生命周期参数丢失错误.

这是一个精简的版本,我希望它是最容易解释的.

解决方案

我需要能够从任何线程内部调用线程池的方法

这要求线程池数据采用互斥机制,比如MutexRwLock(RefCell 不适用于多线程情况,参见 本书).此外,每个线程必须保留对线程池数据的引用,因为线程池存储线程,这就创建了一个 问题.为了解决这个问题,我们可以把线程池数据放在一个Arc 并存储一个 Weak 在每个线程中引用它.请注意,我们使用弱引用来避免引用循环.

<块引用>

这实际上最终会改变 1 个或多个线程(调用者和目标).

这要求线程数据处于互斥机制中.为了完成需求,请注意,由于线程池数据在 Mutex 中,我们不能返回对线程的引用(这将需要保持线程池数据锁定),因此我们也将线程数据放在一个.

这是一个使用 Mutex 的实现示例(游乐场):

使用 std::collections::HashMap;使用 std::sync::{Arc, Weak, Mutex};类型 ID = u32;结构线程池{内部:Arc,}结构线程池内部{池:HashMap>,id_count:ID,}实现线程池{fn new() ->线程池{让内部 = ThreadPoolInner {池:HashMap::new(),id_count: 0,};ThreadPool { 内部:Arc::new(Mutex::new(inner))}}fn create(&self) ->线 {让 mut inner = self.inner.lock().unwrap();让线程=线程{内部:Arc::new(Mutex::new(ThreadInner {id:inner.id_count,池:弧::降级(&self.inner),})),};内部.id_count += 1;让 id = inner.id_count;inner.pool.insert(id, thread.inner.clone());线}fn get(&self, id: Id) ->选项<线程>{让内 = self.inner.lock().unwrap();inner.pool.get(&id).map(|t| 线程{内部:t.clone()})}fn some_mut_method(&self) {让 _inner = self.inner.lock().unwrap();println!("有池的东西");}}结构线程{内部:Arc,}实现线程{fn get_pool(&self) ->选项<线程池>{让内 = self.inner.lock().unwrap();//pool 是弱引用,upgrate 尝试从中获取 Arcinner.pool.upgrade().map(|inner| ThreadPool { 内部:内部})}fn some_mut_method(&self) {如果让 Some(pool) = self.get_pool() {pool.some_mut_method();让 _t2 = pool.get(2).unwrap();println!("t2 的东西");}}}#[派生(克隆)]结构线程内{身份证:身份证,池:弱,}fn 主(){让池 = ThreadPool::new();让 t1 = pool.create();让 _t2 = pool.create();t1.some_mut_method();}

I'm trying to implement a very naive Thread Pool model. For now the responsibilities of the thread pool are:

  • create a new thread and return a reference to it
  • Maintain a map of created threads
  • Mutate threads according to different events triggered by threads

My main problem is that the requirement above forces me to let the thread pool keep a HashMap<Id, Thread> of threads, but also provide a reference to threads when they are created.

On top of that, I need to be able to call methods of the thread pool from inside any thread that effectively end up mutating 1 or more threads (the caller and the targets).

Here is a non functional implementation:

use std::collections::HashMap;
use std::cell::RefCell;

type Id = u32;
type ThreadPoolRef = RefCell<ThreadPool>;

#[derive(Debug)]
struct ThreadPool {
    pool: HashMap<Id, RefCell<Thread>>,
    id_count: Id
}

impl ThreadPool {
    fn new() -> ThreadPool {
        ThreadPool {
            pool: HashMap::new(),
            id_count: 1
        }
    }

    fn create(&mut self) -> &RefCell<Thread> {
        let thread: RefCell<Thread> =
            RefCell::new(
                Thread::new(self.id_count, RefCell::new(self))
            );
        self.id_count = self.id_count + 1;
        self.pool.insert(self.id_count, thread);
        self.pool.get(&self.id_count).unwrap()
    }
}

#[derive(Debug, Clone)]
struct Thread {
    id: Id,
    pool: ThreadPoolRef
}

impl Thread {
    fn new(id: Id, pool: ThreadPoolRef) -> Thread {
        Thread {
            id: id,
            pool: pool
        }
    }
}

fn main() {
    let thread_pool = ThreadPool::new();
    let thread1 = thread_pool.create();
    let thread2 = thread_pool.create();
    // The final goal is to call thread1.method()
    // that mutates thread1 and calls thread_pool.method2()
    // which in turn will call thread2.method3() that will effectively
    // mutate thread 2
}

Rust Play

I tried several things, like the use of RefCell but I started to get a lot of lifetime parameters missing errors.

This is a stripped-down version that I expect to be the most simple to explain.

解决方案

I need to be able to call methods of the thread pool from inside any thread

This requires that the thread pool data be in a mutual-exclusion mechanism, like Mutex or RwLock (RefCell is not appropriate for multi-threaded situations, see the book). Besides, each thread must keep a reference to the thread pool data, as the thread pool stores the threads, this creates a problem. To solve this problem, we can put the thread pool data in an Arc and store a Weak reference to it in each thread. Note that we use weak references to avoid reference cycles.

that effectively end up mutating 1 or more threads (the caller and the targets).

This requires that the thread data be in a mutual-exclusion mechanism. To finalize the requirements, note that as the thread pool data is in a Mutex, we cannot return references to threads (that would require keeping the thread pool data locked), so we also put thread data in a Arc.

Here is an example of implementation using Mutex (Playground):

use std::collections::HashMap;
use std::sync::{Arc, Weak, Mutex};

type Id = u32;

struct ThreadPool {
    inner: Arc<Mutex<ThreadPoolInner>>,
}

struct ThreadPoolInner {
    pool: HashMap<Id, Arc<Mutex<ThreadInner>>>,
    id_count: Id,
}

impl ThreadPool {
    fn new() -> ThreadPool {
        let inner = ThreadPoolInner {
            pool: HashMap::new(),
            id_count: 0,
        };
        ThreadPool { inner: Arc::new(Mutex::new(inner)) }
    }

    fn create(&self) -> Thread {
        let mut inner = self.inner.lock().unwrap();
        let thread = Thread {
            inner: Arc::new(Mutex::new(ThreadInner {
                id: inner.id_count,
                pool: Arc::downgrade(&self.inner),
            })),
        };
        inner.id_count += 1;
        let id = inner.id_count;
        inner.pool.insert(id, thread.inner.clone());
        thread
    }

    fn get(&self, id: Id) -> Option<Thread> {
        let inner = self.inner.lock().unwrap();
        inner.pool.get(&id).map(|t| Thread { inner: t.clone() })
    }

    fn some_mut_method(&self) {
        let _inner = self.inner.lock().unwrap();
        println!("something with pool");
    }
}

struct Thread {
    inner: Arc<Mutex<ThreadInner>>,
}

impl Thread {
    fn get_pool(&self) -> Option<ThreadPool> {
        let inner = self.inner.lock().unwrap();
        // pool is a weak reference, upgrate try to get an Arc from it
        inner.pool.upgrade().map(|inner| ThreadPool { inner: inner })
    }

    fn some_mut_method(&self) {
        if let Some(pool) = self.get_pool() {
            pool.some_mut_method();
            let _t2 = pool.get(2).unwrap();
            println!("something with t2");
        }
    }
}

#[derive(Clone)]
struct ThreadInner {
    id: Id,
    pool: Weak<Mutex<ThreadPoolInner>>,
}

fn main() {
    let pool = ThreadPool::new();
    let t1 = pool.create();
    let _t2 = pool.create();
    t1.some_mut_method();
}

这篇关于Rust 中的共享循环引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆