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

Rust 并发编程

El manejo seguro y eficiente de la concurrencia es uno de los objetivos de Rust, que principalmente resuelve la capacidad de carga alta de los servidores.

El concepto de concurrencia (concurrent) es que diferentes partes del programa se ejecutan de manera independiente, lo que es fácilmente confundido con el concepto de paralelismo (parallel), que enfatiza la "ejecución simultánea".

La concurrencia a menudo lleva a la paralelismo.

Este capítulo trata de conceptos y detalles de programación relacionados con la concurrencia.

Hilo

Un hilo (thread) es una parte independiente que se ejecuta en un programa.

La diferencia entre hilos y procesos (process) es que los hilos son un concepto dentro del programa, y el programa generalmente se ejecuta dentro de un proceso.

En entornos con sistemas operativos, los procesos suelen ser programados alternativamente para ejecutarse, mientras que los hilos se programan dentro del proceso por el programa.

Dado que la concurrencia de hilos puede dar lugar a situaciones paralelas, los errores de bloqueo y demora que pueden ocurrir en la concurrencia suelen aparecer en programas con mecanismos de concurrencia.

Para resolver estos problemas, muchos otros lenguajes (como Java, C#) utilizan software de tiempo de ejecución (runtime) especial para coordinar recursos, pero esto sin duda reduce enormemente la eficiencia de ejecución del programa.

C/C++ El lenguaje en la capa más baja del sistema operativo también admite la multithreading, y el lenguaje en sí y su compilador no tienen la capacidad de detectar y evitar errores paralelos, lo que representa una gran presión para los desarrolladores, quienes deben dedicar una gran cantidad de energía para evitar errores.

Rust no depende del entorno de ejecución, esto es similar a C/C++ igual.

Pero Rust ha diseñado en el lenguaje mismo medios que incluyen el mecanismo de propiedad para eliminar lo más común de los errores en la fase de compilación, algo que otros lenguajes no tienen.

Esto no significa que podamos ser descuidados en la programación, hasta ahora, los problemas causados por la concurrencia no se han resuelto completamente en el ámbito público, aún puede haber errores, ¡se debe ser lo más cuidadoso posible en la programación concurrente!

En Rust, se crea un nuevo proceso a través de la función std::thread::spawn:

use std::thread;
usar std::time::Duration;
fn spawn_function() {
    por i en 0..5 {
        println!("hilo de ejecución generó impresión {}", i);
        hilo::dormir(Duration::desde_milisecundas(1));
    }
}
fn main() {
    thread::spawn(spawn_function);
    por i en 0..3 {
        println!("hilo principal de impresión {}", i);
        hilo::dormir(Duration::desde_milisecundas(1));
    }
}

Resultado de la ejecución:

hilo principal de impresión 0
hilo de ejecución generó impresión 0
hilo principal de impresión 1
hilo de ejecución generó impresión 1
hilo principal de impresión 2
hilo de ejecución generó impresión 2

este resultado puede cambiar de orden en algunas circunstancias, pero en general se imprime así.

Este programa tiene un subhilo, el propósito es imprimir 5 las líneas de texto, el hilo principal imprime tres líneas de texto, pero es obvio que con el final del hilo principal, el hilo spawn también finalizó y no completó todas las impresiones.

el parámetro de la función std::thread::spawn es una función sin parámetros, pero la forma de escritura anterior no es la recomendada, podemos usar closures (closures) para transmitir funciones como parámetros:

use std::thread;
usar std::time::Duration;
fn main() {
    thread::spawn(|| {
        por i en 0..5 {
            println!("hilo de ejecución generó impresión {}", i);
            hilo::dormir(Duration::desde_milisecundas(1));
        }
    });
    por i en 0..3 {
        println!("hilo principal de impresión {}", i);
        hilo::dormir(Duration::desde_milisecundas(1));
    }
}

los closures pueden guardarse en variables o transmitirse como parámetros a otras funciones, son funciones anónimas. Los closures son equivalentes a las expresiones Lambda en Rust, con el siguiente formato:

|parámetro1, parámetro2, ...| -) > tipo de retorno {
    // cuerpo de la función
}

por ejemplo:

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

Resultado de la ejecución:

inc(5) = 6

los closures pueden omitir la declaración de tipos y usar el mecanismo de juicio de tipos automático de Rust:

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

los resultados no cambiaron.

método join

use std::thread;
usar std::time::Duration;
fn main() {
    let handle = thread::spawn(|| {
        por i en 0..5 {
            println!("hilo de ejecución generó impresión {}", i);
            hilo::dormir(Duration::desde_milisecundas(1));
        }
    });
    por i en 0..3 {
        println!("hilo principal de impresión {}", i);
        hilo::dormir(Duration::desde_milisecundas(1));
    }
    handle.join().unwrap();
}

Resultado de la ejecución:

hilo principal de impresión 0 
hilo de ejecución generó impresión 0 
hilo de ejecución generó impresión 1 
hilo principal de impresión 1 
hilo de ejecución generó impresión 2 
hilo principal de impresión 2 
hilo de ejecución generó impresión 3 
hilo de ejecución generó impresión 4

El método join permite que el programa se detenga después de que la subruta finalice su ejecución.

迁移所有权 con move

Esto es un caso comúnmente encontrado:

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

Intentar usar los recursos de la función actual en una subruta, ¡esto definitivamente es incorrecto! Porque el mecanismo de propiedad prohíbe la generación de这种情况,lo que destruirá la determinación de la destrucción de recursos del mecanismo de propiedad. Podemos usar la palabra clave move de la clausura para manejar:

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

Transmisión de mensajes

En Rust, una de las herramientas principales para la implementación de transmisión de mensajes y programación concurrente es el canal (channel), que consta de dos partes: un transmisor (transmitter) y un receptor (receiver).

std::sync::mpsc contiene métodos de transmisión de mensajes:

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!("Obtenido: {}", received);
}

Resultado de la ejecución:

Obtenido: hi

La subruta obtuvo el remitente del hilo principal tx, llamó a su método send y envió una cadena, luego el hilo principal recibió a través del receptor correspondiente rx.