English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Programação Concorrente Rust

O processamento seguro e eficiente da concorrência é um dos objetivos da criação do Rust, que resolve principalmente a capacidade de suporte a alta carga de servidores.

O conceito de concorrência (concurrente) é que diferentes partes do programa executam de forma independente, o que é fácil confundir com o conceito de paralelo (paralelo), que enfatiza "execução simultânea".

A concorrência geralmente leva ao paralelo.

Este capítulo aborda conceitos e detalhes de programação relacionados à concorrência.

Thread

Thread (thread) é uma parte independente de execução de um programa.

A diferença entre thread (processo) e thread é que a thread é um conceito dentro do programa, e o programa geralmente é executado dentro de um processo.

Em ambientes com sistema operacional, os processos são frequentemente escalonados alternadamente para execução, enquanto as threads são escalonadas dentro do processo pelo programa.

Devido à possibilidade de concorrência de threads, deadlock e erros de atraso que podem ocorrer no paralelo são comuns em programas que contêm mecanismos de concorrência.

Para resolver esses problemas, muitas outras linguagens (como Java, C#) adotam software de tempo de execução (runtime) especial para coordenação de recursos, mas isso sem dúvida reduz significativamente a eficiência de execução do programa.

C/C++ A linguagem de nível mais baixo do sistema operacional também suporta multi-threading, e a linguagem em si e seu compilador não possuem a capacidade de detectar e evitar erros paralelos, o que representa uma grande pressão para os desenvolvedores, que precisam gastar muita energia para evitar a ocorrência de erros.

Rust 不依靠运行时环境,这一点像 C/C++ 一样。

但 Rust 在语言本身就设计了包括所有权机制在内的手段来尽可能地把最常见的错误消灭在编译阶段,这一点其他语言不具备。

但这不意味着我们编程的时候可以不小心,迄今为止由于并发造成的问题还没有在公共范围内得到完全解决,仍有可能出现错误,并发编程时要尽量小心!

Rust 中通过 std::thread::spawn 函数创建新进程:

use std::thread;
use std::time::Duration;
fn spawn_function() {
    for i in 0..5 {
        println!("thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
}
fn main() {
    thread::spawn(spawn_function);
    for i in 0..3 {
        println!("main thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
}

Resultado da Execução:

main thread print 0
thread print 0
main thread print 1
thread print 1
main thread print 2
thread print 2

这个结果在某些情况下顺序有可能变化,但总体上是这样打印出来的。

此程序有一个子线程,目的是打印 5 行文字,主线程打印三行文字,但很显然随着主线程的结束,spawn 线程也随之结束了,并没有完成所有打印。

std::thread::spawn 函数的参数是一个无参函数,但上述写法不是推荐的写法,我们可以使用闭包(closures)来传递函数作为参数:

use std::thread;
use std::time::Duration;
fn main() {
    thread::spawn(|| {
        for i in 0..5 {
            println!("thread print {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    for i in 0..3 {
        println!("main thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
}

闭包是可以保存进变量或作为参数传递给其他函数的匿名函数。闭包相当于 Rust 中的 Lambda 表达式,格式如下:

|参数1, 参数2, ...| -> 返回值类型 {
    // 函数体
}

例如:

fn main() {
    let inc = |num: i32| -> i32 {
        num + 1
    });
    println!("inc(5) = {}, inc(5));
}

Resultado da Execução:

inc(5) = 6

闭包可以省略类型声明使用 Rust 自动类型判断机制:

fn main() {
    let inc = |num| {
        num + 1
    });
    println!("inc(5) = {}, inc(5));
}

结果没有变化。

join 方法

use std::thread;
use std::time::Duration;
fn main() {
    let handle = thread::spawn(|| {
        for i in 0..5 {
            println!("thread print {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    for i in 0..3 {
        println!("main thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
    handle.join().unwrap();
}

Resultado da Execução:

main thread print 0 
thread print 0 
thread print 1 
main thread print 1 
thread print 2 
main thread print 2 
thread print 3 
thread print 4

O método join pode fazer com que o programa pare de executar após a subthread terminar.

Transferência Forçada de Propriedade

Esta é uma situação comum que se encontra muitas vezes:

use std::thread;
fn main() {
    let s = "hello";
    
    let handle = thread::spawn(|| {
        println!("{}", s);
    });
    handle.join().unwrap();
}

Tentar usar os recursos da função atual na subthread, isso é certamente errado! Porque o mecanismo de propriedade proíbe a ocorrência dessa situação perigosa, que destruiria a determinação do mecanismo de propriedade ao destruir recursos. Podemos usar a palavra-chave move de闭包 para lidar com isso:

use std::thread;
fn main() {
    let s = "hello";
    
    let handle = thread::spawn(move || {
        println!("{}", s);
    });
    handle.join().unwrap();
}

Transmissão de Mensagens

Uma das principais ferramentas de comunicação de mensagens e concurrency em Rust é o canal (channel), que consiste em dois componentes: um transmitter (transmissor) e um receiver (receptor).

std::sync::mpsc contém métodos de comunicação de mensagens:

use std::thread;
use std::sync::mpsc;
fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });
    let received = rx.recv().unwrap();
    println!("Obtido: {}", received);
}

Resultado da Execução:

Obtido: hi

A subthread obteve o transmitter tx da thread principal e chamou seu método send, enviando uma string, e então a thread principal recebeu através do receiver rx.