Rust 中的线程安全可变交叉引用

问题描述 投票:0回答:1

我想实现两个不同的结构,它们相互引用,可以相互变异,并且是线程安全的。

我可以介绍以下例子:

pub struct Player {
    pub x: f32,
    pub y: f32,
    game: Option<Game>, // Reference to `Game`
}

impl Player {
    pub fn new(x: f32, y: f32) -> Self {    
        Player { x, y, game: None}
    }

    // Some functions that can change `self`, `game`, and `map`
}

pub struct Game {
    pub map: GameMap,
    players: Vec<Player>, // Reference to `Player`
}

impl Game {
    pub fn new() -> Self {
        Game { map: GameMap::new(), players: Vec::new()}
    }

    pub fn register_player(&mut self, player: Player) {
        todo!();
    }
}

我想要一个类似的界面:

fn main() {
    let mut p1 = Player::new(0.0, 0.0);
    let mut p2 = Player::new(100.0, 100.0);

    let mut game = Game::new();
    game.register_player(p1);
    game.register_player(p2);

    p1.forward();  // changes its coordinates using `map` from `game`
    p2.shoot();  // changes the `map` and possibly another player
}

我不能使用

std::rc::Rc
std::cell::RefCell
,因为我的目标是编写线程安全代码。我尝试了
std::sync::{Arc, Mutex}
,但还没有成功。我该如何解决这个挑战?


UPD在这里我添加了我尝试使用互斥体的代码

use std::sync::{Arc, Mutex};

pub struct Player {
    pub x: f32,
    pub y: f32,
    game: Option<Arc<Mutex<Game>>>,
}

impl Player {
    pub fn create(x: f32, y: f32) -> Arc<Mutex<Self>> {
        let mut player = Player {
            x,
            y,
            game: None,
        };
        Arc::new(Mutex::new(player))
    }

    pub fn mount_game(&mut self, game: Arc<Mutex<Game>>) {
        self.game = Some(game);
    }
}

pub struct Game {
    players: Vec<Arc<Mutex<Player>>>,
}

impl Game {
    pub fn create() -> Arc<Mutex<Self>> {
        let mut game = Game {
            players: Vec::new(),
        };
        Arc::new(Mutex::new(game))
    }

    pub fn register_player(&self, game_arc: Arc<Mutex<Self>>, player_arc: Arc<Mutex<Player>>) {
        let mut game = game_arc.lock().unwrap();
        game.players.push(Arc::clone(&player_arc));
        player_arc.lock().unwrap().mount_game(Arc::clone(&game_arc));
    }
}

fn main() {
    let mut p1 = Player::create(0.0, 0.0);
    let mut p2 = Player::create(0.0, 0.0);
    let mut game = Game::create();
    game.lock().unwrap().register_player(Arc::clone(&game), Arc::clone(&p1));
    game.lock().unwrap().register_player(Arc::clone(&game), Arc::clone(&p2));
}
multithreading rust mutex pyo3
1个回答
0
投票

您发布的代码出现死锁,因为您两次锁定游戏互斥锁:

  • 一旦进入
    main
    ,当您致电
    game.lock()
  • 然后在
    register_player
    当你打电话给
    game_arc.lock()
    时。

此外,从 API 的角度来看,需要将游戏两次传递给

register_player
(一次作为
self
,一次作为
game_arc
)是相当尴尬的。

现在,如果我们去掉

Game
周围的互斥锁,这样我们就有了
Arc<Game>
,那么我们可以将
register_player
声明为:

pub fn register_player (self: &Arc<Game>, player_arc: Arc<Mutex<Player>>)

游乐场

但是如果我们希望能够对其进行变异,我们需要将

Mutex
放入
Game
中:

pub struct Game {
    players: Mutex<Vec<Arc<Mutex<Player>>>>,
    // Here: ^^^^^
}

在这种情况下,

Game
包含单个字段,因此很容易,但假设您的游戏状态更复杂,那么使用起来会很痛苦。

如果我们能够告诉编译器

register_player
采用
&Arc<Mutex<Self>>
,那就太好了,但不幸的是,我们只能使用
Deref
Self
Mutex
不能使用的类型 (*)。我们也不能像这样直接在
register_player
上添加
Mutex<Game>
方法:

// Not allowed:
impl Mutex<Game> {
    pub fn register_player (self: &Arc<Mutex<Game>>, player_arc: Arc<Mutex<Player>>) {
        todo!();
    }
}

因为

Mutex
是外国类型。然而,我们可以使用扩展特征来伪造它:

pub trait GameExt {
    pub fn register_player (self: &Arc<Self>, player_arc: Arc<Mutex<Player>>);
}

impl GameExt for Mutex<Game> {
    fn register_player (self: &Arc<Self>, player_arc: Arc<Mutex<Player>>) {
        todo!();
    }
}

最后,在

Arc<Player>
内有
Game
和在
Arc<Game>
内有
Player
是一个坏主意,因为它有引入内存泄漏的风险:即使您删除所有其他引用,它们仍然会互相引用,因此它们各自引用计数永远不会达到 0,并且永远不会被释放。可以通过用
Arc
 指针替换其中一个 
Weak
来避免这种情况,这会打破循环。

完整的工作示例(我还删除了一堆多余的

mut
,因为
Arc<Mutex<_>>
值在锁定之前不需要是
mut
):

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

pub struct Player {
    pub x: f32,
    pub y: f32,
    game: Option<Weak<Mutex<Game>>>,
}

impl Player {
    pub fn create (x: f32, y: f32) -> Arc<Mutex<Self>> {
        let player = Player {
            x,
            y,
            game: None,
        };
        Arc::new (Mutex::new (player))
    }

    pub fn mount_game (&mut self, game: Arc<Mutex<Game>>) {
        self.game = Some (Arc::downgrade (&game));
    }
    
    pub fn modify_game_state (&self) {
        self.game.as_ref().unwrap().upgrade().unwrap().lock().unwrap().state = 42;
    }
}

pub struct Game {
    state: i32,
    players: Vec<Arc<Mutex<Player>>>,
}

impl Game {
    pub fn new() -> Arc<Mutex<Self>> {
        let game = Game {
            state: 0,
            players: Vec::new(),
        };
        Arc::new (Mutex::new (game))
    }
}

pub trait GameExt {
    fn register_player (self: &Arc<Self>, player_arc: Arc<Mutex<Player>>);
}

impl GameExt for Mutex<Game> {
    fn register_player (self: &Arc<Self>, player_arc: Arc<Mutex<Player>>) {
        player_arc.lock().unwrap().mount_game (Arc::clone(self));
        let mut game = self.lock().unwrap();
        game.players.push (player_arc);
    }
}

fn main() {
    let p1 = Player::create (0.0, 0.0);
    let p2 = Player::create (0.0, 0.0);
    let game = Game::new();
    game.register_player (p1); // or Arc::clone (&p1) if you need to use p1 later in this function
    game.register_player (Arc::clone (&p2));
    p2.lock().unwrap().modify_game_state();
}

游乐场


(*)

Mutex<Foo>
不能
Deref
Foo
,因为在引用处于活动状态时需要将
MutexGuard
放置在某个地方,而
Deref
不提供放置防护装置的任何地方。

© www.soinside.com 2019 - 2024. All rights reserved.