Rust语言之GoF设计模式:单例模式

22-09-25 banq

单例Singleton能让您确保仅存在一个同类对象,同时提供对该实例的全局访问点。

Singleton 是一个全局可变对象,就Rust而言, 它是一个static mut项目,这反过来意味着 它需要一个unsafe块 来读取或写入可变静态变量。
一方面,它可以被视为不安全的模式;
但另一方面,Singleton 在实践中被用于 Rust。

在Rust中实现Singleton的一个纯粹安全的方法是完全不使用全局变量,通过函数参数传递一切。这非常类似Java中提倡的。

//! 在Rust中实现Singleton的一个纯粹安全的方法是不使用静态变量
//! 并通过函数参数传递一切。
//! 最古老的活体变量是在`main()`开始时创建的一个对象。

fn change(global_state: &mut u32) {
    *global_state += 1;
}

fn main() {
    let mut global_state = 0u32;

    change(&mut global_state);

    println! ("最终状态: {}", global_state);
}


从Rust 1.63开始,Mutex::new是常量,你可以使用全局静态Mutex锁而不需要懒惰的初始化。见下面的mutex.rs例子。

//!Ructc 1.63
//! https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton
//!
//! 从Rust 1.63开始,使用全局可变的单例
//虽然在大多数情况下,避免使用全局变量仍然是最好的选择。
//!情况下,还是要避免使用全局变量。
//!
//! 现在`Mutex::new`是`const`,你可以使用全局静态的`Mutex`锁了
//! 而不需要懒惰的初始化。

use std::sync::Mutex。

static ARRAY: Mutex<Vec<i32>> = Mutex::new(Vec::new() )。

fn do_a_call() {
    ARRAY.lock().unwrap().push(1)。
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println! ("Called {} times", ARRAY.lock().unwrap().len() );
}


还有一种懒惰单例:声明一个静态变量,并在第一次访问时进行懒惰初始化。
它实际上是通过不安全的静态Mut操作来实现的,然而,它使你的代码没有不安全的块。

lazy.rs

//! 取自: https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton
//!
//! Rust并不允许没有`unsafe`的单子模式,因为它
//! 没有一个安全的可变全局状态。
//!
//! `lazy-static`允许声明一个静态变量,并在第一次访问时进行懒惰初始化
//! 第一次访问时。它实际上是通过`unsafe`与`static mut`实现的。
//! 操作,然而,它使你的代码没有`不安全`块。
//!
//! `Mutex`提供对单个对象的安全访问。

use lazy_static::lazy_static;
use std::sync::Mutex;

lazy_static! {
    static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec! );
}

fn do_a_call() {
    ARRAY.lock().unwrap().push(1)。
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println! ("Called {}", ARRAY.lock().unwrap().len() );
}


以上单例模式的理解侧重于全局变量的角度,其实单例还存在多线程模式下单线程执行的情况:

在任何编程语言中,单例一般都不容易安全实现,尤其是在多线程环境中。然而,Rust强调(可能是保证)内存和并发安全,再加上一些额外的语言约束,使得单例的实现在最好的情况下也很难,在最坏的情况下也很烦人。

在单例设计过程中必须做出几个决定:
  • 初始化(何时/如何/何处;静态/惰性;一次/多次;可配置或硬编码)
  • 可用性(始终可用或不可用,与初始化有关)
  • 可变/不可变单例状态
  • 可访问性/可发现性(全局可访问与否;多线程安全与否)
  • 销毁(是否有特殊要求,何时/如何/何地)
  • 对测试的影响
  • (防止多次实例化)

详细见这里

单例在并发编程中防止多次实例化:

Java并发编程中双重检查锁漏洞