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

برمجة التوازي في Rust

معالجة التوازي بشكل آمن وفعال هو واحد من أهداف إنشاء Rust، وهو يركز بشكل رئيسي على قدرة الخدمات على تحمل الأحمال العالية.

المفهوم المتوازي هو تنفيذ أجزاء مختلفة من البرنامج بشكل مستقل، مما يمكن أن يكون مشوشًا مع المفهوم المتوازي، الذي يشدد على "التنفيذ في نفس الوقت".

التوازي يمكن أن يؤدي إلى التنفيذ المتوازي.

هذا الفصل يشرح المفاهيم والتفاصيل المتعلقة بالبرمجة المتوازية.

الاتصال

الاتصال هو جزء مستقل من البرنامج يعمل بشكل مستقل.

الفرق بين الاتصالات والعمليات هو أن الاتصالات هي مفهومًا داخل البرنامج، ويتم تنفيذ البرنامج غالبًا داخل عملية.

في بيئة النظام المضيف، يتم تحديد عملية التشغيل بشكل متبادل، بينما يتم تحديد الاتصالات داخل العملية من قبل البرنامج.

بسبب احتمالية ظهور الوضع المتوازي في التوازي، فإن الأخطاء مثل العائق والاستنساخ التي قد تواجهها في التوازي غالبًا ما تحدث في البرامج التي تحتوي على ميكانيزمات التوازي.

لحل هذه المشاكل، تأخذ العديد من اللغات الأخرى (مثل Java،C#) برامج تشغيل خاصة لتنسيق الموارد، مما يقلل بلا شك من كفاءة تنفيذ البرنامج.

لغة C/C++ تدعم متعدد الاتصالات في أعمق مستويات النظام المضيف، ولا تتوفر لديها القدرة على استكشاف أو تجنب الأخطاء المتعلقة بالتوازي، مما يسبب ضغطًا كبيرًا على المطورين الذين يجب أن يصرفوا الكثير من الوقت لتجنب حدوث الأخطاء.

Rust لا تعتمد على بيئة التشغيل، وهي تشبه C/C++.

لكن لغة Rust قد قامت بتصميم وسائل داخل اللغة نفسها تشمل نظام الملكية لقضاء على أكثر الأخطاء شيوعًا في مرحلة التجميع، وهو ما لا يتمتع به أي لغة أخرى.

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

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

use std::thread;
use std::time::Duration;
fn spawn_function() {
    for i in 0..5 {
        println!("spawned 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));
    }
}

نتيجة التنفيذ:

main thread print 0
spawned thread print 0
main thread print 1
spawned thread print 1
main thread print 2
spawned 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!("spawned 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));
}

نتيجة التنفيذ:

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!("spawned 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();
}

نتيجة التنفيذ:

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

يمكن استخدام طريقة join لمنع توقف البرنامج حتى تنتهي النواة الفرعية من التنفيذ.

تحويل الملكية بقوة

هذا هو الوضع الشائع الذي يظهر:

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

تجربة استخدام موارد الدالة الحالية في النواة الفرعية، هذا هو خطأ محتمل لأن مبدأ الملكية يمنع هذا الوضع الخطير، سيؤدي إلى تدمير مبدأ الملكية وتدمير التأكد من تدمير الموارد.

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

نقل الرسائل

أحد الأدوات الرئيسية لتحقيق نقل الرسائل المتوازية في Rust هو قناة الاتصال (channel)، وتتكون القناة من جزأين، المرسل (transmitter) والمرسل (receiver).

يحتوي std::sync::mpsc على طرق نقل الرسائل:

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!("الحصولت: {}", received);
}

نتيجة التنفيذ:

الحصولت: hi

الحسنة الحصولت على المرسل tx من النواة الرئيسية، وأطلقت طريقة الارسال، ثم استلمت النواة الرئيسية المرسل rx.