Многопоточность является одним из важных аспектов современной разработки программного обеспечения, позволяя программам одновременно выполнять несколько задач, что в конечном итоге может повысить производительность приложения и скорость реагирования. Ключевым элементом многопоточного программированиия является поток (thread). Для простоты под потоком можно понимать сущность, которая выполняет некоторую задачу.
Для создания потоков Rust предоставляет функцию thread::spawn(). В эту функцию в качестве параметра передается другая функция - действие, которое собственно и будет выполнять поток. А результатом функции будет объект, который представляет созданный поток и через который можно управлять потоком. Например:
use std::thread; // подключем функциональность потоков
fn main() {
// создаем и запускаем один поток
thread::spawn(|| {
println!("My Thread");
});
println!("End");
}
Здесь в качестве действия в thread::spawn() передается лямбда-выражение, которое не имеет параметров и которое просто выводит на консоль строку "My Thread".
Однако если мы запустим приложение, то скорее всего мы не увидим на консоли вывод строки "My Thread". Вполне вероятно результат работы программы будет следующим:
End
Таким образом, хотя поток и был запущен, но функция main отработала быстрее, и соответственно программа завершила свою работу. И нам надо сделать, чтобы поток успел выполниться до конца работы программы. Для этого мы можем у объекта потока вызвать метод join(), который будет ожидать завершения потока:
use std::thread; // подключем функциональность потоков
fn main() {
// создаем и запускаем один поток
let my_thread = thread::spawn(|| {
println!("My Thread");
});
// ожидаем завершения потока
my_thread.join().unwrap();
println!("End");
}
Метод join() возвращает объект Result, к которому далее применяется метод unwrap(). В итоге мы получим теперь следующий результат:
My Thread End
Подобным образом можно запускать и выполнять и большее количество потоков:
use std::thread;
fn main() {
let mut threads = vec![]; // вектор для хранения всех потоков
// создаем и запускаем 3 потока
for _ in 1..4 {
let my_thread = thread::spawn(|| {
println!("Thread executed");
});
threads.push(my_thread); // добавляем поток в вектор
}
// ожидаем завершения всех потоков
for t in threads {
t.join().unwrap();
}
println!("End");
}
Здесь создается три потока - все они добавляются в вектор threads. Консольный вывод:
Thread executed Thread executed Thread executed End
Для потоков применяются все те же самые концепции владения и заимствования, как и в целом в Rust. Так, только один поток может одновременно владеть некоторыми данными. Для передачи права владения в поток применяется слово move. Допустим, мы хотим передать в поток из-вне какие-нибудь данные. Например:
use std::thread;
fn main() {
let data = String::from("Hello"); // данные для потока
let my_thread = thread::spawn(move || { // move - передача владения
// владение data перешло в поток
println!("{}", data); //
});
// println!("{}", data); // ! Ошибка - value borrowed here after move
my_thread.join().unwrap();
}
Здесь владение строкой data переходит в запускаемый поток:
let my_thread = thread::spawn(move || { // move - передача владения
println!("{}", data); // владение data перешло в поток
});
Соответственно после этого в главном потоке - функции main мы уже не сможем использовать строку data.