MST

星途 面试题库

面试题:Rust UnsafeCell在多线程场景下的风险与规避

假设你正在编写一个多线程Rust程序,其中使用了UnsafeCell。请描述可能出现的数据竞争场景,并说明如何运用Rust的同步原语(如Mutex、RwLock等)来规避由UnsafeCell带来的并发隐患。
44.5万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

可能出现的数据竞争场景

  1. 多个线程直接读写UnsafeCell允许内部可变性,即可以绕过Rust的不可变借用规则。如果多个线程同时通过UnsafeCell直接读写其包含的数据,就会产生数据竞争。例如:
use std::cell::UnsafeCell;

struct SharedData {
    data: UnsafeCell<i32>,
}

let shared = SharedData { data: UnsafeCell::new(0) };
let threads: Vec<_> = (0..10).map(|_| {
    let shared = &shared;
    std::thread::spawn(move || {
        let raw_ptr = shared.data.get();
        unsafe {
            *raw_ptr += 1;
        }
    })
}).collect();

for thread in threads {
    thread.join().unwrap();
}

在上述代码中,多个线程同时对UnsafeCell中的i32数据进行修改,没有任何同步机制,这就导致了数据竞争。

运用同步原语规避并发隐患

  1. 使用MutexMutex(互斥锁)可以确保同一时间只有一个线程能够访问被保护的数据。将UnsafeCell包裹在Mutex中,就可以控制对UnsafeCell数据的访问。例如:
use std::cell::UnsafeCell;
use std::sync::{Arc, Mutex};

struct SharedData {
    data: UnsafeCell<i32>,
}

let shared = Arc::new(Mutex::new(SharedData { data: UnsafeCell::new(0) }));
let threads: Vec<_> = (0..10).map(|_| {
    let shared = shared.clone();
    std::thread::spawn(move || {
        let mut inner = shared.lock().unwrap();
        let raw_ptr = inner.data.get();
        unsafe {
            *raw_ptr += 1;
        }
    })
}).collect();

for thread in threads {
    thread.join().unwrap();
}

这里通过MutexSharedData包裹,每个线程在访问UnsafeCell中的数据前,先获取Mutex的锁,这样就避免了数据竞争。

  1. 使用RwLock:如果对数据的读取操作远多于写入操作,可以使用RwLock(读写锁)。它允许多个线程同时进行读操作,但只允许一个线程进行写操作。例如:
use std::cell::UnsafeCell;
use std::sync::{Arc, RwLock};

struct SharedData {
    data: UnsafeCell<i32>,
}

let shared = Arc::new(RwLock::new(SharedData { data: UnsafeCell::new(0) }));
let threads: Vec<_> = (0..10).map(|_| {
    let shared = shared.clone();
    std::thread::spawn(move || {
        let inner = shared.read().unwrap();
        let raw_ptr = inner.data.get();
        unsafe {
            println!("Read data: {}", *raw_ptr);
        }
    })
}).collect();

for thread in threads {
    thread.join().unwrap();
}

在这个例子中,多个线程可以同时读取UnsafeCell中的数据,因为读操作是共享的。如果有写操作,需要获取写锁,这样就保证了写操作的原子性,避免了数据竞争。

通过使用MutexRwLock等同步原语包裹UnsafeCell,可以有效地规避UnsafeCell带来的并发隐患,确保多线程程序的安全性。