我正试图让我的头围绕Rust。我的alpha版本为1。
这是我正在尝试编程的问题:我有一个浮动向量。我想异步设置一些线程。每个线程应该等待向量的每个元素指定的秒数,并返回元素的值加上10.结果需要按输入顺序。
这是一个人为的例子,当然,但我想看看我是否可以在转向更复杂的代码之前实现一些简单的东西。到目前为止,这是我的代码:
use std::thread;
use std::old_io::timer;
use std::time::duration::Duration;
fn main() {
let mut vin = vec![1.4f64, 1.2f64, 1.5f64];
let mut guards: Vec<thread::scoped> = Vec::with_capacity(3);
let mut answers: Vec<f64> = Vec::with_capacity(3);
for i in 0..3 {
guards[i] = thread::scoped( move || {
let ms = (1000.0f64 * vin[i]) as i64;
let d = Duration::milliseconds(ms);
timer::sleep(d);
println!("Waited {}", vin[i]);
answers[i] = 10.0f64 + (vin[i] as f64);
})};
for i in 0..3 {guards[i].join(); };
for i in 0..3 {println!("{}", vin[i]); }
}
因此输入向量为[1.4, 1.2, 1.5]
,我期望输出向量为[11.4, 11.2, 11.5]
。
我的代码似乎存在许多问题,但第一个是我收到编译错误:
threads.rs:7:25: 7:39 error: use of undeclared type name `thread::scoped`
threads.rs:7 let mut guards: Vec<thread::scoped> = Vec::with_capacity(3);
^~~~~~~~~~~~~~
error: aborting due to previous error
似乎还存在许多其他问题,包括在闭包中使用vin
。另外,我不知道move
做了什么,除了我见过的每个例子似乎都使用它。
答案 0 :(得分:5)
您的错误是由于thread::scoped
是一个函数,而不是一个类型。你想要的是Vec<T>
,其中T
是函数的结果类型。 Rust有一个简洁的功能,可以帮助您:在许多情况下,它会自动检测变量的正确类型。
如果你使用
let mut guards = Vec::with_capacity(3);
首次使用guards
时,系统会选择.push()
的类型。
似乎还有许多其他问题。
您在第一个for循环中访问guards[i]
,但guards
向量的长度为0
。它的容量为3
,这意味着只要向量永远不包含3个以上的元素,就不会有任何不必要的分配。使用guards.push(x)
代替guards[i] = x
。
thread::scoped
需要Fn() -> T
,因此您的闭包可以返回一个对象。当你拨打.join()
时,你会得到那个对象,所以你不需要答案向量。
vin
被移至封闭处。因此,在创建警卫的循环的第二次迭代中,vin
不再可用于移动到“第二”闭包。每次循环迭代都会创建一个新的闭包。
i
被移至封闭处。我不知道那里发生了什么。但解决方案是在闭包之外let inval = vin[i];
,然后在闭包内使用inval
。这也解决了第3点。
vin
是可变的。但你永远不会改变它。如果您不需要,请不要将变量绑定。
vin
是f64
的数组。因此(vin[i] as f64)
什么都不做。因此,您只需直接使用vin[i]
。
join
离开后卫。由于无法移出数组,因此无法将索引编入索引数组并在指定索引处加入元素。你可以做的是遍历数组的元素并加入每个守卫。
基本上这意味着:不要迭代索引(for i in 1..3
),而是尽可能迭代元素(for element in vector
)。
以上所有实施:
use std::thread;
use std::old_io::timer;
use std::time::duration::Duration;
fn main() {
let vin = vec![1.4f64, 1.2f64, 1.5f64];
let mut guards = Vec::with_capacity(3);
for inval in vin {
guards.push(thread::scoped( move || {
let ms = (1000.0f64 * inval) as i64;
let d = Duration::milliseconds(ms);
timer::sleep(d);
println!("Waited {}", inval);
10.0f64 + inval
}));
}
for guard in guards {
let answer = guard.join();
println!("{}", answer);
};
}
答案 1 :(得分:1)
补充Ker的答案:如果你真的需要在一个线程中改变数组,我想最适合你的任务的有效解决方案是这样的:
use std::thread::spawn;
use std::old_io::timer;
use std::sync::{Arc, Mutex};
use std::time::duration::Duration;
fn main() {
let vin = Arc::new(vec![1.4f64, 1.2f64, 1.5f64]);
let answers = Arc::new(Mutex::new(vec![0f64, 0f64, 0f64]));
let mut workers = Vec::new();
for i in 0..3 {
let worker_vin = vin.clone();
let worker_answers = answers.clone();
let worker = spawn( move || {
let ms = (1000.0f64 * worker_vin[i]) as i64;
let d = Duration::milliseconds(ms);
timer::sleep(d);
println!("Waited {}", worker_vin[i]);
let mut answers = worker_answers.lock().unwrap();
answers[i] = 10.0f64 + (worker_vin[i] as f64);
});
workers.push(worker);
}
for worker in workers { worker.join().unwrap(); }
for answer in answers.lock().unwrap().iter() {
println!("{}", answer);
}
}
为了在多个线程之间共享向量,我必须证明,这些向量比我的所有线程都要长。我不能只使用Vec
,因为它会在main
块的末尾被销毁,而另一个线程可以活得更长,可能会访问释放的内存。所以我拿了Arc
引用计数器,它保证只有当计数器降到零时才会销毁我的向量。
Arc
允许我分享只读数据。为了改变answers
数组,我应该使用一些同步工具,比如Mutex
。这就是Rust阻止我进行数据竞赛的方式。