Rust Once初始化与线程安全
Rust Once初始化机制
在Rust中,Once
类型是 std::sync::Once
,它提供了一种在程序中进行一次性初始化的机制。这种初始化机制非常重要,尤其是在多线程环境下,能够确保某些代码只执行一次,避免重复初始化带来的问题。
Once
的基本原理
Once
类型内部使用了原子操作来实现线程安全的一次性初始化。它通过一个 AtomicUsize
字段来跟踪初始化状态。AtomicUsize
是Rust标准库中提供的用于原子操作的类型,它允许在多线程环境下对 usize
类型的值进行无锁的原子读写操作。
当使用 Once
进行初始化时,首先会检查 AtomicUsize
中的值,判断初始化是否已经完成。如果尚未完成,就会进入初始化流程,在初始化完成后,将 AtomicUsize
的值设置为表示初始化完成的状态。由于 AtomicUsize
的原子操作特性,多个线程同时尝试初始化时,只有一个线程能够成功执行初始化代码,其他线程会等待初始化完成。
Once
的使用方法
Once
类型提供了 call_once
方法来执行一次性初始化。call_once
方法接受一个闭包作为参数,这个闭包中的代码就是需要进行一次性执行的初始化代码。
下面是一个简单的单线程示例:
use std::sync::Once;
static INIT: Once = Once::new();
fn setup() {
println!("Initializing...");
}
fn main() {
INIT.call_once(setup);
INIT.call_once(setup);
}
在这个示例中,INIT
是一个 Once
类型的静态变量。main
函数中两次调用 INIT.call_once(setup)
,但由于 Once
的特性,setup
函数只会被执行一次。运行这个程序,你会看到输出:
Initializing...
Once
与线程安全
在多线程环境下,Once
的线程安全特性就显得尤为重要。Rust的 Once
类型设计初衷就是为了在多线程程序中提供可靠的一次性初始化机制。
多线程下的 Once
使用
考虑以下多线程示例:
use std::sync::{Once, Arc};
use std::thread;
static INIT: Once = Once::new();
fn setup() {
println!("Initializing...");
}
fn main() {
let num_threads = 10;
let threads: Vec<_> = (0..num_threads).map(|_| {
thread::spawn(move || {
INIT.call_once(setup);
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
}
在这个示例中,我们创建了10个线程,每个线程都尝试调用 INIT.call_once(setup)
。由于 Once
的线程安全特性,setup
函数只会被执行一次,无论有多少个线程同时尝试初始化。运行这个程序,你仍然只会看到一次 Initializing...
的输出。
Once
线程安全的实现细节
从实现层面来看,Once
使用了 AtomicUsize
的 compare_and_swap
(在Rust中通常使用 compare_exchange
方法)原子操作。这个操作会比较 AtomicUsize
当前的值与预期值,如果相等,则将其设置为新值,并返回旧值;如果不相等,则返回当前值。
在 Once
的初始化过程中,首先会检查 AtomicUsize
的值是否表示初始化已经完成。如果没有完成,线程会尝试使用 compare_exchange
操作将其设置为表示正在初始化的状态。只有第一个成功将其设置为正在初始化状态的线程能够继续执行初始化代码,其他线程会进入等待状态。当初始化完成后,AtomicUsize
的值会被设置为表示初始化完成的状态,等待中的线程会再次检查状态,发现已经初始化完成后,就不会再尝试执行初始化代码。
结合 Once
与 Lazy
Rust还提供了 std::sync::Lazy
类型,它与 Once
密切相关,并且在很多场景下结合使用会更加方便。
Lazy
的基本概念
Lazy
类型用于延迟初始化一个值。它内部实际上是基于 Once
实现的,提供了一种更简洁的方式来定义和使用延迟初始化的值。Lazy
类型在首次访问其值时会执行初始化闭包,并且确保初始化只发生一次,这与 Once
的一次性初始化特性相契合。
Lazy
的使用示例
use std::sync::Lazy;
static DATA: Lazy<String> = Lazy::new(|| {
println!("Initializing data...");
"Hello, Lazy!".to_string()
});
fn main() {
println!("Accessing data: {}", DATA);
println!("Accessing data again: {}", DATA);
}
在这个示例中,DATA
是一个 Lazy<String>
类型的静态变量。Lazy::new
接受一个闭包,闭包中的代码会在首次访问 DATA
时执行,用于初始化 DATA
的值。运行这个程序,你会看到输出:
Initializing data...
Accessing data: Hello, Lazy!
Accessing data again: Hello, Lazy!
可以看到,初始化代码只执行了一次,即使多次访问 DATA
。
多线程下的 Lazy
Lazy
在多线程环境下同样是线程安全的。下面是一个多线程使用 Lazy
的示例:
use std::sync::{Lazy, Arc};
use std::thread;
static DATA: Lazy<String> = Lazy::new(|| {
println!("Initializing data...");
"Hello, Lazy in threads!".to_string()
});
fn main() {
let num_threads = 10;
let threads: Vec<_> = (0..num_threads).map(|_| {
thread::spawn(move || {
println!("Thread accessing data: {}", DATA);
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
}
在这个示例中,10个线程同时尝试访问 DATA
。由于 Lazy
基于 Once
实现,初始化代码只会在第一个访问 DATA
的线程中执行一次,其他线程会等待初始化完成后直接使用已初始化的值。运行这个程序,你会看到初始化输出只出现一次,每个线程都能正确访问到初始化后的值。
Once
与静态变量初始化
在Rust中,Once
常常用于静态变量的初始化,以确保静态变量在多线程环境下的正确初始化。
静态变量的初始化问题
在多线程程序中,静态变量的初始化需要特别小心。如果没有合适的机制,可能会出现多个线程同时尝试初始化静态变量的情况,导致未定义行为或数据竞争。例如:
// 以下代码存在数据竞争风险
static mut DATA: i32 = 0;
fn init_data() {
unsafe {
if DATA == 0 {
DATA = 42;
}
}
}
fn main() {
let num_threads = 10;
let threads: Vec<_> = (0..num_threads).map(|_| {
thread::spawn(move || {
init_data();
unsafe {
println!("Thread sees data: {}", DATA);
}
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
}
在这个示例中,init_data
函数尝试在 DATA
未初始化时进行初始化。但是由于没有同步机制,多个线程可能同时进入 if DATA == 0
块,导致数据竞争。
使用 Once
解决静态变量初始化问题
通过使用 Once
,可以确保静态变量的初始化是线程安全的。以下是修正后的代码:
use std::sync::Once;
static DATA: i32;
static INIT: Once = Once::new();
fn init_data() {
INIT.call_once(|| {
DATA = 42;
});
}
fn main() {
let num_threads = 10;
let threads: Vec<_> = (0..num_threads).map(|_| {
thread::spawn(move || {
init_data();
println!("Thread sees data: {}", DATA);
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
}
在这个版本中,INIT
是一个 Once
类型的静态变量,init_data
函数使用 INIT.call_once
来确保 DATA
只被初始化一次。无论有多少个线程调用 init_data
,DATA
的初始化都只会发生一次,从而避免了数据竞争问题。
Once
在复杂场景中的应用
除了基本的一次性初始化和静态变量初始化,Once
在一些复杂场景中也有重要应用。
初始化全局资源
在一些应用中,可能需要初始化一些全局资源,如数据库连接池、网络服务等。这些资源的初始化通常是昂贵的,并且只需要进行一次。使用 Once
可以有效地管理这些全局资源的初始化。
use std::sync::{Once, Mutex};
use std::collections::HashMap;
// 模拟数据库连接
struct DatabaseConnection {
// 实际的数据库连接相关字段
}
impl DatabaseConnection {
fn new() -> Self {
println!("Connecting to database...");
DatabaseConnection {}
}
}
static DB_CONNECTION: Mutex<Option<DatabaseConnection>> = Mutex::new(None);
static INIT_DB: Once = Once::new();
fn get_database_connection() -> DatabaseConnection {
INIT_DB.call_once(|| {
let mut guard = DB_CONNECTION.lock().unwrap();
*guard = Some(DatabaseConnection::new());
});
DB_CONNECTION.lock().unwrap().as_ref().unwrap().clone()
}
fn main() {
let num_threads = 10;
let threads: Vec<_> = (0..num_threads).map(|_| {
thread::spawn(move || {
let conn = get_database_connection();
// 使用数据库连接进行操作
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
}
在这个示例中,DatabaseConnection
模拟了一个数据库连接。DB_CONNECTION
是一个 Mutex
保护的 Option<DatabaseConnection>
,用于存储数据库连接实例。INIT_DB
是 Once
类型,确保数据库连接只初始化一次。get_database_connection
函数使用 INIT_DB.call_once
来初始化数据库连接,并在后续调用中返回已初始化的连接。
动态初始化与配置
在一些情况下,初始化过程可能需要根据运行时的配置进行动态调整。Once
与 Lazy
结合可以很好地满足这种需求。
use std::sync::{Once, Lazy};
use std::env;
// 假设这是一个根据配置动态初始化的对象
struct ConfiguredObject {
value: i32,
}
impl ConfiguredObject {
fn new() -> Self {
let config_value: i32 = match env::var("CONFIG_VALUE") {
Ok(val) => val.parse().unwrap_or(42),
Err(_) => 42,
};
ConfiguredObject { value: config_value }
}
}
static CONFIGURED_OBJECT: Lazy<ConfiguredObject> = Lazy::new(|| {
println!("Initializing ConfiguredObject...");
ConfiguredObject::new()
});
fn main() {
println!("Using configured object: {}", CONFIGURED_OBJECT.value);
}
在这个示例中,ConfiguredObject
的初始化依赖于环境变量 CONFIG_VALUE
。Lazy
类型确保 ConfiguredObject
只在首次访问时初始化,并且 Once
保证了初始化的线程安全性。这样,无论在单线程还是多线程环境下,ConfiguredObject
都能根据运行时配置正确初始化。
Once
的性能考量
虽然 Once
提供了可靠的线程安全一次性初始化机制,但在性能敏感的场景中,也需要考虑其带来的开销。
原子操作的开销
Once
内部使用的 AtomicUsize
原子操作在现代硬件上已经非常高效,但仍然比普通的内存读写操作要慢。尤其是在频繁调用 call_once
的情况下,原子操作的开销可能会变得明显。例如,在一个性能关键的循环中,如果每次迭代都调用 call_once
,即使初始化已经完成,原子操作的开销也会累积。
为了减少这种开销,可以尽量将 Once
的初始化放在程序启动阶段,或者在初始化完成后避免不必要的 call_once
调用。例如,可以在初始化完成后设置一个标志,在后续代码中直接检查这个标志,而不是每次都调用 call_once
。
初始化闭包的开销
call_once
接受的闭包中的代码也是性能考量的一部分。如果闭包中的初始化代码非常复杂或耗时,那么这将直接影响到初始化的性能。在这种情况下,可以考虑将复杂的初始化逻辑分解为多个步骤,或者使用异步初始化的方式来避免阻塞主线程或其他关键线程。
例如,对于一些需要网络请求或磁盘I/O的初始化操作,可以使用Rust的异步编程模型(如 async/await
)来进行异步初始化,从而在初始化过程中不影响其他线程的执行。
Once
与其他同步原语的比较
在Rust中,除了 Once
,还有其他同步原语可以用于解决初始化和线程安全问题,了解它们之间的差异有助于选择合适的工具。
Once
与 Mutex
Mutex
是Rust中常用的互斥锁,用于保护共享资源的访问。与 Once
不同,Mutex
主要用于控制对共享资源的并发访问,而不是一次性初始化。
如果只是需要保护某个资源在多线程环境下的读写操作,Mutex
是合适的选择。但如果需要确保某个初始化过程只执行一次,Mutex
本身并不能提供这种保证。例如,使用 Mutex
来实现一次性初始化可能会导致多次初始化的问题:
use std::sync::Mutex;
static mut DATA: i32 = 0;
static MUTEX: Mutex<()> = Mutex::new(());
fn init_data() {
let _lock = MUTEX.lock().unwrap();
unsafe {
if DATA == 0 {
DATA = 42;
}
}
}
fn main() {
let num_threads = 10;
let threads: Vec<_> = (0..num_threads).map(|_| {
thread::spawn(move || {
init_data();
unsafe {
println!("Thread sees data: {}", DATA);
}
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
}
在这个示例中,虽然使用了 Mutex
来保护对 DATA
的访问,但由于多个线程可能同时进入 if DATA == 0
块,仍然可能出现多次初始化的情况。而 Once
能够确保初始化只发生一次。
Once
与 Condvar
Condvar
是条件变量,用于线程间的同步通信,通常与 Mutex
结合使用。Condvar
主要用于线程等待某个条件满足后再继续执行,而不是一次性初始化。
例如,一个线程可能需要等待另一个线程完成某个任务后才能继续执行,这时可以使用 Condvar
。但对于一次性初始化的需求,Condvar
并不是合适的工具。Once
专注于确保初始化的一次性和线程安全性,与 Condvar
的功能有明显区别。
总结 Once
的适用场景
Once
在Rust中是一个非常有用的工具,适用于多种场景:
- 多线程环境下的一次性初始化:无论是初始化全局资源、静态变量,还是其他需要确保只初始化一次的场景,
Once
都能提供可靠的线程安全保证。 - 延迟初始化:结合
Lazy
类型,Once
可以实现延迟初始化,在首次访问时才进行初始化操作,并且保证初始化的线程安全性。 - 复杂初始化逻辑:当初始化过程涉及动态配置、资源获取等复杂操作时,
Once
能够确保这些操作在多线程环境下正确执行且只执行一次。
通过深入理解 Once
的原理、使用方法和性能考量,开发者可以在Rust程序中更好地利用这一机制,编写高效、线程安全的代码。在实际应用中,根据具体场景选择合适的同步原语,并合理优化初始化过程,是编写高质量Rust多线程程序的关键。
希望通过本文的介绍,你对Rust的 Once
初始化与线程安全机制有了更深入的理解,并能在自己的项目中灵活运用。如果你在使用过程中遇到任何问题或有进一步的思考,欢迎与社区交流分享。
继续拓展关于 Once
的更多细节,我们来看一些更深入的场景。
Once
在跨模块初始化中的应用
在大型Rust项目中,代码通常会被组织成多个模块。在这种情况下,Once
对于跨模块的一次性初始化同样非常有用。
模块间共享资源的初始化
假设我们有一个项目,其中有多个模块需要使用同一个数据库连接。我们可以使用 Once
来确保数据库连接在整个项目中只初始化一次,并且在各个模块中都能安全地获取到已初始化的连接。
// db.rs
use std::sync::{Once, Mutex};
// 模拟数据库连接
struct DatabaseConnection {
// 实际的数据库连接相关字段
}
impl DatabaseConnection {
fn new() -> Self {
println!("Connecting to database...");
DatabaseConnection {}
}
}
pub static DB_CONNECTION: Mutex<Option<DatabaseConnection>> = Mutex::new(None);
pub static INIT_DB: Once = Once::new();
pub fn get_database_connection() -> DatabaseConnection {
INIT_DB.call_once(|| {
let mut guard = DB_CONNECTION.lock().unwrap();
*guard = Some(DatabaseConnection::new());
});
DB_CONNECTION.lock().unwrap().as_ref().unwrap().clone()
}
// module1.rs
use super::{get_database_connection, DB_CONNECTION};
pub fn module1_function() {
let conn = get_database_connection();
// 使用数据库连接进行操作
let _guard = DB_CONNECTION.lock().unwrap();
// 其他与数据库连接相关的操作
}
// module2.rs
use super::{get_database_connection, DB_CONNECTION};
pub fn module2_function() {
let conn = get_database_connection();
// 使用数据库连接进行操作
let _guard = DB_CONNECTION.lock().unwrap();
// 其他与数据库连接相关的操作
}
// main.rs
mod db;
mod module1;
mod module2;
fn main() {
module1::module1_function();
module2::module2_function();
}
在这个示例中,db.rs
模块定义了数据库连接的初始化逻辑,并通过 Once
确保连接只初始化一次。module1.rs
和 module2.rs
模块都可以通过调用 get_database_connection
函数来获取已初始化的数据库连接,从而在不同模块间共享这个资源,同时保证了线程安全。
跨模块静态变量的初始化
类似地,对于跨模块的静态变量初始化,Once
也能发挥重要作用。假设我们有一个配置相关的静态变量,多个模块都需要使用:
// config.rs
use std::sync::{Once, Mutex};
// 假设这是一个配置结构体
struct Config {
value: i32,
}
impl Config {
fn new() -> Self {
Config { value: 42 }
}
}
pub static CONFIG: Mutex<Option<Config>> = Mutex::new(None);
pub static INIT_CONFIG: Once = Once::new();
pub fn get_config() -> Config {
INIT_CONFIG.call_once(|| {
let mut guard = CONFIG.lock().unwrap();
*guard = Some(Config::new());
});
CONFIG.lock().unwrap().as_ref().unwrap().clone()
}
// module_a.rs
use super::{get_config, CONFIG};
pub fn module_a_function() {
let config = get_config();
// 使用配置进行操作
let _guard = CONFIG.lock().unwrap();
// 其他与配置相关的操作
}
// module_b.rs
use super::{get_config, CONFIG};
pub fn module_b_function() {
let config = get_config();
// 使用配置进行操作
let _guard = CONFIG.lock().unwrap();
// 其他与配置相关的操作
}
// main.rs
mod config;
mod module_a;
mod module_b;
fn main() {
module_a::module_a_function();
module_b::module_b_function();
}
在这个例子中,config.rs
模块负责初始化 Config
结构体,并通过 Once
确保其只初始化一次。module_a.rs
和 module_b.rs
模块可以通过 get_config
函数获取已初始化的配置,实现了跨模块静态变量的线程安全初始化和共享。
Once
在错误处理场景中的应用
在实际编程中,初始化过程可能会失败,因此需要考虑如何在 Once
初始化机制中处理错误。
处理初始化闭包中的错误
call_once
方法接受的闭包通常不允许返回错误,因为 Once
的设计初衷是确保初始化只执行一次,而错误处理可能会导致多次尝试初始化。然而,我们可以通过一些变通的方法来处理初始化错误。
一种方法是在闭包中使用 Result
类型,并将错误存储在一个共享的变量中。例如:
use std::sync::{Once, Mutex};
// 模拟一个可能失败的初始化函数
fn initialize() -> Result<(), &'static str> {
// 这里模拟一个可能失败的初始化操作
if rand::random::<bool>() {
Ok(())
} else {
Err("Initialization failed")
}
}
static INIT_RESULT: Mutex<Option<Result<(), &'static str>>> = Mutex::new(None);
static INIT: Once = Once::new();
fn get_initialized() -> Result<(), &'static str> {
INIT.call_once(|| {
let result = initialize();
let mut guard = INIT_RESULT.lock().unwrap();
*guard = Some(result);
});
INIT_RESULT.lock().unwrap().as_ref().unwrap().clone()
}
fn main() {
match get_initialized() {
Ok(()) => println!("Initialization successful"),
Err(e) => println!("Initialization failed: {}", e),
}
}
在这个示例中,initialize
函数模拟了一个可能失败的初始化操作。INIT_RESULT
用于存储初始化结果,INIT
确保 initialize
只被调用一次。get_initialized
函数在调用 call_once
后返回存储在 INIT_RESULT
中的初始化结果,从而实现了对初始化错误的处理。
重试初始化
在某些情况下,我们可能希望在初始化失败时进行重试。虽然 Once
本身不支持直接重试,但我们可以通过额外的逻辑来实现。
use std::sync::{Once, Mutex};
use std::thread;
use std::time::Duration;
// 模拟一个可能失败的初始化函数
fn initialize() -> Result<(), &'static str> {
// 这里模拟一个可能失败的初始化操作
if rand::random::<bool>() {
Ok(())
} else {
Err("Initialization failed")
}
}
static INIT_RESULT: Mutex<Option<Result<(), &'static str>>> = Mutex::new(None);
static INIT: Once = Once::new();
fn get_initialized() -> Result<(), &'static str> {
let mut attempt = 0;
loop {
INIT.call_once(|| {
let result = initialize();
let mut guard = INIT_RESULT.lock().unwrap();
*guard = Some(result);
});
let result = INIT_RESULT.lock().unwrap().as_ref().unwrap().clone();
if result.is_ok() {
return result;
}
if attempt >= 3 {
return result;
}
attempt += 1;
thread::sleep(Duration::from_secs(1));
}
}
fn main() {
match get_initialized() {
Ok(()) => println!("Initialization successful"),
Err(e) => println!("Initialization failed: {}", e),
}
}
在这个版本中,get_initialized
函数在初始化失败时会进行最多3次重试,每次重试间隔1秒。通过这种方式,我们在 Once
的基础上实现了初始化失败时的重试机制。
Once
在异步编程中的应用
随着Rust异步编程模型的广泛应用,了解 Once
在异步场景中的使用也很重要。
异步初始化与 Once
在异步代码中,我们可能需要一次性初始化一些异步资源,如异步数据库连接池、异步网络客户端等。虽然 Once
本身是基于同步操作的,但我们可以通过一些技巧来实现异步一次性初始化。
一种常见的方法是使用 OnceCell
和 async_once
库。OnceCell
是 std::cell::OnceCell
,它提供了一种类似 Once
的一次性初始化机制,但适用于非 'static
生命周期的值。async_once
库则进一步扩展了 OnceCell
,使其支持异步初始化。
首先,添加依赖:
[dependencies]
async_once = "1.0"
然后,使用示例如下:
use async_once::OnceCell;
use std::sync::Arc;
use tokio::sync::Mutex;
// 模拟异步数据库连接
struct AsyncDatabaseConnection {
// 实际的异步数据库连接相关字段
}
impl AsyncDatabaseConnection {
async fn new() -> Self {
println!("Connecting to async database...");
AsyncDatabaseConnection {}
}
}
static ASYNC_DB_CONNECTION: OnceCell<Arc<Mutex<AsyncDatabaseConnection>>> = OnceCell::new();
async fn get_async_database_connection() -> Arc<Mutex<AsyncDatabaseConnection>> {
ASYNC_DB_CONNECTION.get_or_init(|| async {
Arc::new(Mutex::new(AsyncDatabaseConnection::new().await))
}).await
}
#[tokio::main]
async fn main() {
let conn1 = get_async_database_connection().await;
let conn2 = get_async_database_connection().await;
assert!(Arc::ptr_eq(&conn1, &conn2));
}
在这个示例中,OnceCell
和 async_once
库结合使用,确保 AsyncDatabaseConnection
只被异步初始化一次。get_async_database_connection
函数使用 get_or_init
方法,该方法接受一个异步闭包,在首次调用时执行异步初始化操作,并返回已初始化的值。
异步线程安全与 Once
在异步多线程环境中,Once
的线程安全特性同样重要。async_once
库实现的异步一次性初始化也是线程安全的,这意味着在多个异步任务同时尝试初始化时,只有一个任务会执行初始化操作,其他任务会等待初始化完成。
例如,假设有多个异步任务需要访问同一个异步初始化的资源:
use async_once::OnceCell;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::task;
// 模拟异步数据库连接
struct AsyncDatabaseConnection {
// 实际的异步数据库连接相关字段
}
impl AsyncDatabaseConnection {
async fn new() -> Self {
println!("Connecting to async database...");
AsyncDatabaseConnection {}
}
}
static ASYNC_DB_CONNECTION: OnceCell<Arc<Mutex<AsyncDatabaseConnection>>> = OnceCell::new();
async fn get_async_database_connection() -> Arc<Mutex<AsyncDatabaseConnection>> {
ASYNC_DB_CONNECTION.get_or_init(|| async {
Arc::new(Mutex::new(AsyncDatabaseConnection::new().await))
}).await
}
async fn async_task() {
let conn = get_async_database_connection().await;
// 使用异步数据库连接进行操作
}
#[tokio::main]
async fn main() {
let num_tasks = 10;
let tasks: Vec<_> = (0..num_tasks).map(|_| {
task::spawn(async move {
async_task().await;
})
}).collect();
for task in tasks {
task.await.unwrap();
}
}
在这个示例中,10个异步任务同时尝试获取异步数据库连接。由于 OnceCell
和 async_once
库的线程安全机制,AsyncDatabaseConnection
只会被初始化一次,所有任务都能安全地获取到已初始化的连接。
通过以上对 Once
在不同场景下的应用介绍,相信你对 Once
的功能和使用方法有了更全面的认识。在实际开发中,根据具体需求合理运用 Once
,能够有效地提高代码的质量和性能,确保多线程和异步环境下的程序正确性。