Multithreading 当它';这不是必需的
我正在编写一个游戏,其玩家列表定义如下:Multithreading 当它';这不是必需的,multithreading,rust,mutex,Multithreading,Rust,Mutex,我正在编写一个游戏,其玩家列表定义如下: pub struct PlayerList { by_name: HashMap<String, Arc<Mutex<Player>>>, by_uuid: HashMap<Uuid, Arc<Mutex<Player>>>, } NetworkServer { ... player_list: Arc<Mutex<PlayerList&
pub struct PlayerList {
by_name: HashMap<String, Arc<Mutex<Player>>>,
by_uuid: HashMap<Uuid, Arc<Mutex<Player>>>,
}
NetworkServer {
...
player_list: Arc<Mutex<PlayerList>>,
...
}
Server {
...
player_list: Arc<Mutex<PlayerList>>,
...
}
这在Arc
中,因为NetworkServer
访问不同线程(网络循环)中的列表。当玩家加入时,会为他们生成一个线程,并将其添加到玩家列表中 虽然我正在做的唯一操作是添加到
player_列表
,但我不得不使用Arc
而不是HashMap
s中更自然的Rc
,因为互斥体
需要它。我不是从网络线程(或任何其他线程)访问播放器,因此将它们置于互斥锁下是没有意义的。只有HashMap
s需要锁定,我正在使用Mutex
进行锁定。但生锈是迂腐的,它想防止一切滥用
由于我只访问主线程中的Player
s,因此每次锁定都会很烦人,性能也会降低。是否有替代使用不安全
或其他方法的解决方法
下面是一个例子:
use std::cell::Cell;
use std::collections::HashMap;
use std::ffi::CString;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::thread;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct Uuid([u8; 16]);
struct Player {
pub name: String,
pub uuid: Uuid,
}
struct PlayerList {
by_name: HashMap<String, Arc<Mutex<Player>>>,
by_uuid: HashMap<Uuid, Arc<Mutex<Player>>>,
}
impl PlayerList {
fn add_player(&mut self, p: Player) {
let name = p.name.clone();
let uuid = p.uuid;
let p = Arc::new(Mutex::new(p));
self.by_name.insert(name, Arc::clone(&p));
self.by_uuid.insert(uuid, p);
}
}
struct NetworkServer {
player_list: Arc<Mutex<PlayerList>>,
}
impl NetworkServer {
fn start(&mut self) {
let player_list = Arc::clone(&self.player_list);
thread::spawn(move || {
loop {
// fake network loop
// listen for incoming connections, accept player and add them to player_list.
player_list.lock().unwrap().add_player(Player {
name: "blahblah".into(),
uuid: Uuid([0; 16]),
});
}
});
}
}
struct Server {
player_list: Arc<Mutex<PlayerList>>,
network_server: NetworkServer,
}
impl Server {
fn start(&mut self) {
self.network_server.start();
// main game loop
loop {
// I am only accessing players in this loop in this thread. (main thread)
// so Mutex for individual player is not needed although rust requires it.
}
}
}
fn main() {
let player_list = Arc::new(Mutex::new(PlayerList {
by_name: HashMap::new(),
by_uuid: HashMap::new(),
}));
let network_server = NetworkServer {
player_list: Arc::clone(&player_list),
};
let mut server = Server {
player_list,
network_server,
};
server.start();
}
使用std::cell::cell;
使用std::collections::HashMap;
使用std::ffi::CString;
使用std::rc::rc;
使用std::sync::{Arc,Mutex};
使用std::线程;
#[派生(克隆、复制、PartialEq、Eq、散列)]
结构Uuid([u8;16]);
结构播放器{
酒吧名称:String,
发布uuid:uuid,
}
结构玩家列表{
姓名:HashMap,
作者:HashMap,
}
impl玩家列表{
fn添加玩家(&mut self,p:player){
让name=p.name.clone();
设uuid=p.uuid;
设p=Arc::new(Mutex::new(p));
self.by_name.insert(name,Arc::clone(&p));
自身按uuid插入(uuid,p);
}
}
结构网络服务器{
玩家名单:Arc,
}
impl网络服务器{
fn启动(&M自我){
让player\u list=Arc::clone(&self.player\u list);
线程::生成(移动| |{
环路{
//假网络环路
//侦听传入连接,接受播放机并将其添加到播放机列表中。
玩家列表。锁定()。展开()。添加玩家(玩家){
名称:“blahblah”.into(),
uuid:uuid([0;16]),
});
}
});
}
}
结构服务器{
玩家名单:Arc,
网络服务器:网络服务器,
}
impl服务器{
fn启动(&M自我){
self.network_server.start();
//主游戏循环
环路{
//我只在这个线程中访问这个循环中的玩家。(主线程)
//所以,虽然rust需要为单个玩家提供互斥锁,但它并不需要。
}
}
}
fn main(){
让玩家列表=弧::新(互斥体::新(玩家列表{
按名称:HashMap::new(),
按:HashMap::new(),
}));
让网络服务器=网络服务器{
玩家列表:Arc::克隆(&玩家列表),
};
让mut server=server{
球员名单,
网络服务器,
};
server.start();
}
发送
是一种标记特征,用于控制哪些对象的所有权可以跨线程边界转移。对于完全由Send
类型组成的任何类型,它都会自动实现。这也是一个不安全的特性,因为手动实现此特性可能会导致编译器无法执行我们喜欢的关于Rust的并发安全性
问题是,Rc
不是Send
,因此您的PlayerList
不是Send
,因此无法发送到另一个线程,即使在Arc
中包装。unsafe
解决方法是为您的PlayerList
结构发送unsafe impl
将此代码放入游乐场示例中,可以使用Arc
struct PlayerList{
姓名:HashMap,
作者:HashMap,
}
播放列表{}的不安全impl发送
impl玩家列表{
fn添加玩家(&mut self,p:player){
让name=p.name.clone();
设uuid=p.uuid;
设p=Rc::new(RefCell::new(p));
self.by_name.insert(name,Rc::clone(&p));
自身按uuid插入(uuid,p);
}
}
遗憾的是,在解释程序员在对包含Rc
s的类型不安全地实现Send
时必须强制执行哪些规则时,本文的内容有点少,但仅在一个线程中访问似乎足够安全
为了完整起见
由于我只在主线程中访问玩家
,因此每次锁定玩家都很烦人,而且性能较差
你的意思是,因为现在你只在主线程中访问玩家
,但是在以后的任何时候,你可能会意外地在另一个线程中引入对他们的访问
从语言的角度来看,如果可以获得对某个值的引用,则可以使用该值。因此,如果多个线程引用了一个值,那么从多个线程使用该值应该是安全的。在编译时,没有办法强制执行某个特定的值(虽然可以访问),但实际上从未使用过
然而,这提出了一个问题:
如果给定线程从未使用该值,那么为什么该线程首先可以访问它
在我看来,你有一个设计问题。如果您可以设法重新设计程序,以便只有主线程可以访问播放器列表
,那么您将立即能够使用Rc
例如,您可以让网络线程向主线程发送一条消息,宣布新的播放器已连接
目前,你正在“通过分享进行交流”,你可以改为“通过交流进行分享”。前者通常到处都有同步原语(如互斥体、原子等),可能会面临争用/死锁问题,而后者通常有通信队列(通道),需要“异步”
struct PlayerList {
by_name: HashMap<String, Rc<RefCell<Player>>>,
by_uuid: HashMap<Uuid, Rc<RefCell<Player>>>,
}
unsafe impl Send for PlayerList {}
impl PlayerList {
fn add_player(&mut self, p: Player) {
let name = p.name.clone();
let uuid = p.uuid;
let p = Rc::new(RefCell::new(p));
self.by_name.insert(name, Rc::clone(&p));
self.by_uuid.insert(uuid, p);
}
}