Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

শেয়ারড-স্টেট কনকারেন্সি (Shared-State Concurrency)

মেসেজ পাসিং কনকারেন্সি পরিচালনা করার একটি চমৎকার উপায়, কিন্তু এটিই একমাত্র উপায় নয়। আরেকটি পদ্ধতি হলো একাধিক থ্রেডের একই শেয়ারড ডেটা অ্যাক্সেস করা। গো ল্যাঙ্গুয়েজের ডকুমেন্টেশনের স্লোগানের এই অংশটি আবার বিবেচনা করুন: "মেমরি শেয়ার করে যোগাযোগ করবেন না।"

মেমরি শেয়ার করে যোগাযোগ করা দেখতে কেমন হবে? এছাড়াও, মেসেজ পাসিংয়ের উৎসাহীরা কেন মেমরি শেয়ারিং ব্যবহার না করার জন্য সাবধান করে?

একভাবে, যেকোনো প্রোগ্রামিং ল্যাঙ্গুয়েজে চ্যানেলগুলো একক মালিকানার (single ownership) মতো কারণ একবার আপনি একটি চ্যানেলের মাধ্যমে একটি ভ্যালু স্থানান্তর করলে, আপনার আর সেই ভ্যালুটি ব্যবহার করা উচিত নয়। শেয়ারড-মেমরি কনকারেন্সি একাধিক মালিকানার (multiple ownership) মতো: একাধিক থ্রেড একই সময়ে একই মেমরি লোকেশন অ্যাক্সেস করতে পারে। যেমনটি আপনি অধ্যায় ১৫-এ দেখেছেন, যেখানে স্মার্ট পয়েন্টার একাধিক মালিকানা সম্ভব করেছিল, একাধিক মালিকানা জটিলতা বাড়াতে পারে কারণ এই বিভিন্ন মালিকদের পরিচালনা করার প্রয়োজন হয়। Rust-এর টাইপ সিস্টেম এবং মালিকানার নিয়মগুলো এই পরিচালনা সঠিকভাবে করতে ব্যাপকভাবে সহায়তা করে। একটি উদাহরণ হিসাবে, আসুন আমরা মিউটেক্স (mutexes) দেখি, যা শেয়ারড মেমোরির জন্য অন্যতম সাধারণ কনকারেন্সি প্রিমিটিভ।

এক সময়ে একটি থ্রেড থেকে ডেটা অ্যাক্সেসের অনুমতি দেওয়ার জন্য মিউটেক্স ব্যবহার করা

মিউটেক্স (Mutex) হলো মিউচুয়াল এক্সক্লুশন (mutual exclusion) এর একটি সংক্ষিপ্ত রূপ, যেমন একটি মিউটেক্স যেকোনো সময়ে শুধুমাত্র একটি থ্রেডকে কিছু ডেটা অ্যাক্সেস করার অনুমতি দেয়। একটি মিউটেক্সের ডেটা অ্যাক্সেস করার জন্য, একটি থ্রেডকে প্রথমে মিউটেক্সের লক (lock) অর্জন করার জন্য অনুরোধ করে অ্যাক্সেস চাওয়ার সংকেত দিতে হয়। লক হলো একটি ডেটা স্ট্রাকচার যা মিউটেক্সের অংশ এবং এটি ট্র্যাক রাখে যে বর্তমানে কার ডেটাতে একচেটিয়া অ্যাক্সেস রয়েছে। অতএব, মিউটেক্সকে লকিং সিস্টেমের মাধ্যমে তার ধারণ করা ডেটা গার্ড (guarding) করছে বলে বর্ণনা করা হয়।

মিউটেক্স ব্যবহার করা কঠিন বলে একটি খ্যাতি আছে কারণ আপনাকে দুটি নিয়ম মনে রাখতে হবে:

১. ডেটা ব্যবহার করার আগে আপনাকে অবশ্যই লক অর্জন করার চেষ্টা করতে হবে। ২. যখন মিউটেক্স দ্বারা সুরক্ষিত ডেটার সাথে আপনার কাজ শেষ হয়ে যায়, তখন আপনাকে অবশ্যই ডেটা আনলক করতে হবে যাতে অন্য থ্রেডগুলো লক অর্জন করতে পারে।

মিউটেক্সের একটি বাস্তব-জগতের উপমা হিসাবে, একটি সম্মেলনের প্যানেল আলোচনার কথা কল্পনা করুন যেখানে শুধুমাত্র একটি মাইক্রোফোন রয়েছে। একজন প্যানেলিস্ট কথা বলার আগে, তাকে মাইক্রোফোন ব্যবহার করতে চাওয়ার জন্য অনুরোধ বা সংকেত দিতে হবে। যখন সে মাইক্রোফোন পায়, তখন সে যতক্ষণ চায় কথা বলতে পারে এবং তারপর পরবর্তী প্যানেলিস্টকে মাইক্রোফোনটি হস্তান্তর করে যে কথা বলতে অনুরোধ করে। যদি একজন প্যানেলিস্ট কথা শেষ করে মাইক্রোফোনটি হস্তান্তর করতে ভুলে যায়, তবে অন্য কেউ কথা বলতে পারে না। যদি শেয়ারড মাইক্রোফোনের ব্যবস্থাপনায় ভুল হয়, প্যানেলটি পরিকল্পনা অনুযায়ী কাজ করবে না!

মিউটেক্সের ব্যবস্থাপনা সঠিকভাবে করা অবিশ্বাস্যভাবে কঠিন হতে পারে, যে কারণে এত লোক চ্যানেল সম্পর্কে উত্সাহী। যাইহোক, Rust-এর টাইপ সিস্টেম এবং মালিকানার নিয়মগুলোর জন্য ধন্যবাদ, আপনি লকিং এবং আনলকিংয়ে ভুল করতে পারবেন না।

Mutex<T> এর API

একটি মিউটেক্স কীভাবে ব্যবহার করতে হয় তার একটি উদাহরণ হিসাবে, আসুন আমরা একটি একক-থ্রেডেড প্রেক্ষাপটে একটি মিউটেক্স ব্যবহার করে শুরু করি, যেমনটি তালিকা ১৬-১২-এ দেখানো হয়েছে।

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

    println!("m = {m:?}");
}

অনেক টাইপের মতোই, আমরা অ্যাসোসিয়েটেড ফাংশন new ব্যবহার করে একটি Mutex<T> তৈরি করি। মিউটেক্সের ভিতরের ডেটা অ্যাক্সেস করার জন্য, আমরা লক অর্জন করতে lock মেথড ব্যবহার করি। এই কলটি বর্তমান থ্রেডটিকে ব্লক করবে যাতে এটি কোনো কাজ করতে না পারে যতক্ষণ না আমাদের লক পাওয়ার পালা আসে।

lock এর কলটি ব্যর্থ হবে যদি লক ধরে রাখা অন্য কোনো থ্রেড প্যানিক করে। সেক্ষেত্রে, কেউ কখনও লকটি পেতে পারবে না, তাই আমরা unwrap করতে বেছে নিয়েছি এবং যদি আমরা সেই পরিস্থিতিতে থাকি তবে এই থ্রেডটি প্যানিক করবে।

আমরা লকটি অর্জন করার পরে, আমরা রিটার্ন ভ্যালুটিকে, এই ক্ষেত্রে যার নাম num, ভিতরের ডেটার একটি মিউটেবল রেফারেন্স (mutable reference) হিসাবে ব্যবহার করতে পারি। টাইপ সিস্টেম নিশ্চিত করে যে m-এর ভ্যালু ব্যবহার করার আগে আমরা একটি লক অর্জন করি। m-এর টাইপ হলো Mutex<i32>, i32 নয়, তাই i32 ভ্যালুটি ব্যবহার করতে সক্ষম হওয়ার জন্য আমাদের অবশ্যই lock কল করতে হবে। আমরা ভুলতে পারি না; টাইপ সিস্টেম অন্যথায় আমাদের ভিতরের i32 অ্যাক্সেস করতে দেবে না।

lock এর কলটি MutexGuard নামের একটি টাইপ রিটার্ন করে, যা একটি LockResult-এ মোড়ানো থাকে যা আমরা unwrap-এর কল দিয়ে হ্যান্ডেল করেছি। MutexGuard টাইপটি আমাদের ভিতরের ডেটার দিকে নির্দেশ করতে Deref ইমপ্লিমেন্ট করে; এই টাইপের একটি Drop ইমপ্লিমেন্টেশনও রয়েছে যা MutexGuard স্কোপের বাইরে চলে গেলে স্বয়ংক্রিয়ভাবে লকটি ছেড়ে দেয়, যা ভিতরের স্কোপের শেষে ঘটে। ফলস্বরূপ, আমরা লকটি ছেড়ে দিতে ভুলে যাওয়ার এবং মিউটেক্সটিকে অন্য থ্রেড দ্বারা ব্যবহৃত হতে ব্লক করার ঝুঁকি নিই না কারণ লক রিলিজ স্বয়ংক্রিয়ভাবে ঘটে।

লকটি ড্রপ করার পরে, আমরা মিউটেক্সের ভ্যালু প্রিন্ট করতে পারি এবং দেখতে পারি যে আমরা ভিতরের i32 কে 6-এ পরিবর্তন করতে সক্ষম হয়েছি।

একাধিক থ্রেডের মধ্যে একটি Mutex<T> শেয়ার করা

এখন আসুন একাধিক থ্রেডের মধ্যে Mutex<T> ব্যবহার করে একটি ভ্যালু শেয়ার করার চেষ্টা করি। আমরা ১০টি থ্রেড চালু করব এবং তাদের প্রত্যেককে একটি কাউন্টার ভ্যালু ১ করে বাড়াতে বলব, যাতে কাউন্টার ০ থেকে ১০ পর্যন্ত যায়। তালিকা ১৬-১৩-এর উদাহরণটিতে একটি কম্পাইলার এরর থাকবে, এবং আমরা সেই এররটি Mutex<T> ব্যবহার সম্পর্কে এবং Rust কীভাবে আমাদের এটি সঠিকভাবে ব্যবহার করতে সাহায্য করে সে সম্পর্কে আরও জানতে ব্যবহার করব।

use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Mutex::new(0);
    let mut handles = vec![];

    for _ in 0..10 {
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();

            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

আমরা একটি counter ভ্যারিয়েবল তৈরি করি যা একটি Mutex<T>-এর ভিতরে একটি i32 ধারণ করে, যেমনটি আমরা তালিকা ১৬-১২-এ করেছি। এরপর, আমরা একটি সংখ্যার পরিসরের উপর ইটারেট করে ১০টি থ্রেড তৈরি করি। আমরা thread::spawn ব্যবহার করি এবং সমস্ত থ্রেডকে একই ক্লোজার দিই: একটি যা কাউন্টারটিকে থ্রেডে منتقل করে, lock মেথড কল করে Mutex<T>-এর উপর একটি লক অর্জন করে, এবং তারপর মিউটেক্সের ভ্যালুতে ১ যোগ করে। যখন একটি থ্রেড তার ক্লোজার চালানো শেষ করে, num স্কোপের বাইরে চলে যাবে এবং লকটি ছেড়ে দেবে যাতে অন্য থ্রেড এটি অর্জন করতে পারে।

মূল থ্রেডে, আমরা সমস্ত জয়েন হ্যান্ডেল সংগ্রহ করি। তারপর, যেমনটি আমরা তালিকা ১৬-২-এ করেছি, আমরা প্রতিটি হ্যান্ডেলের উপর join কল করি যাতে নিশ্চিত করা যায় যে সমস্ত থ্রেড শেষ হয়েছে। সেই সময়ে, মূল থ্রেডটি লক অর্জন করবে এবং এই প্রোগ্রামের ফলাফল প্রিন্ট করবে।

আমরা ইঙ্গিত দিয়েছিলাম যে এই উদাহরণটি কম্পাইল হবে না। এখন আসুন জেনে নেওয়া যাক কেন!

$ cargo run
   Compiling shared-state v0.1.0 (file:///projects/shared-state)
error[E0382]: borrow of moved value: `counter`
  --> src/main.rs:21:29
   |
5  |     let counter = Mutex::new(0);
   |         ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait
...
8  |     for _ in 0..10 {
   |     -------------- inside of this loop
9  |         let handle = thread::spawn(move || {
   |                                    ------- value moved into closure here, in previous iteration of loop
...
21 |     println!("Result: {}", *counter.lock().unwrap());
   |                             ^^^^^^^ value borrowed here after move
   |
help: consider moving the expression out of the loop so it is only moved once
   |
8  ~     let mut value = counter.lock();
9  ~     for _ in 0..10 {
10 |         let handle = thread::spawn(move || {
11 ~             let mut num = value.unwrap();
   |

For more information about this error, try `rustc --explain E0382`.
error: could not compile `shared-state` (bin "shared-state") due to 1 previous error

এরর মেসেজটি বলছে যে counter ভ্যালুটি লুপের আগের ইটারেশনে সরানো হয়েছে। Rust আমাদের বলছে যে আমরা লক counter-এর মালিকানা একাধিক থ্রেডে منتقل করতে পারি না। আসুন আমরা অধ্যায় ১৫-এ আলোচনা করা একাধিক মালিকানার পদ্ধতি দিয়ে কম্পাইলার এররটি ঠিক করি।

একাধিক থ্রেডের সাথে একাধিক মালিকানা

অধ্যায় ১৫-এ, আমরা একটি রেফারেন্স কাউন্টেড ভ্যালু তৈরি করতে স্মার্ট পয়েন্টার Rc<T> ব্যবহার করে একটি ভ্যালুকে একাধিক মালিক দিয়েছিলাম। আসুন এখানে একই কাজ করি এবং দেখি কী হয়। আমরা তালিকা ১৬-১৪-এ Mutex<T> কে Rc<T>-তে মোড়াব এবং থ্রেডে মালিকানা منتقل করার আগে Rc<T> ক্লোন করব।

use std::rc::Rc;
use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Rc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Rc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();

            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

আবারও, আমরা কম্পাইল করি এবং... ভিন্ন এরর পাই! কম্পাইলার আমাদের অনেক কিছু শেখাচ্ছে।

$ cargo run
   Compiling shared-state v0.1.0 (file:///projects/shared-state)
error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
  --> src/main.rs:11:36
   |
11 |           let handle = thread::spawn(move || {
   |                        ------------- ^------
   |                        |             |
   |  ______________________|_____________within this `{closure@src/main.rs:11:36: 11:43}`
   | |                      |
   | |                      required by a bound introduced by this call
12 | |             let mut num = counter.lock().unwrap();
13 | |
14 | |             *num += 1;
15 | |         });
   | |_________^ `Rc<Mutex<i32>>` cannot be sent between threads safely
   |
   = help: within `{closure@src/main.rs:11:36: 11:43}`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`
note: required because it's used within this closure
  --> src/main.rs:11:36
   |
11 |         let handle = thread::spawn(move || {
   |                                    ^^^^^^^
note: required by a bound in `spawn`
  --> /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181/library/std/src/thread/mod.rs:728:1

For more information about this error, try `rustc --explain E0277`.
error: could not compile `shared-state` (bin "shared-state") due to 1 previous error

বাহ, এই এরর মেসেজটি খুব শব্দবহুল! এখানে মনোযোগ দেওয়ার গুরুত্বপূর্ণ অংশটি হলো: `Rc<Mutex<i32>>` cannot be sent between threads safely। কম্পাইলার আমাদের কারণটিও বলছে: the trait `Send` is not implemented for `Rc<Mutex<i32>>`। আমরা পরবর্তী বিভাগে Send সম্পর্কে কথা বলব: এটি এমন একটি ট্রেইট যা নিশ্চিত করে যে আমরা থ্রেডের সাথে যে টাইপগুলো ব্যবহার করি তা কনকারেন্ট পরিস্থিতিতে ব্যবহারের জন্য তৈরি।

দুর্ভাগ্যবশত, Rc<T> থ্রেড জুড়ে শেয়ার করার জন্য নিরাপদ নয়। যখন Rc<T> রেফারেন্স কাউন্ট পরিচালনা করে, তখন এটি clone-এর প্রতিটি কলের জন্য কাউন্ট যোগ করে এবং প্রতিটি ক্লোন ড্রপ করা হলে কাউন্ট থেকে বিয়োগ করে। কিন্তু এটি কোনো কনকারেন্সি প্রিমিটিভ ব্যবহার করে না যাতে নিশ্চিত করা যায় যে কাউন্টের পরিবর্তনগুলো অন্য কোনো থ্রেড দ্বারা বাধাগ্রস্ত হতে না পারে। এটি ভুল গণনার কারণ হতে পারে—সূক্ষ্ম বাগ যা ফলস্বরূপ মেমরি লিক বা আমাদের কাজ শেষ হওয়ার আগে একটি ভ্যালু ড্রপ হয়ে যাওয়ার কারণ হতে পারে। আমাদের যা প্রয়োজন তা হলো এমন একটি টাইপ যা হুবহু Rc<T>-এর মতো, কিন্তু যা রেফারেন্স কাউন্টের পরিবর্তনগুলো একটি থ্রেড-সেফ উপায়ে করে।

Arc<T> এর সাথে অ্যাটমিক রেফারেন্স কাউন্টিং

ভাগ্যক্রমে, Arc<T> হলো Rc<T>-এর মতো একটি টাইপ যা কনকারেন্ট পরিস্থিতিতে ব্যবহার করার জন্য নিরাপদ। a এর অর্থ হলো অ্যাটমিক, যার মানে এটি একটি অ্যাটমিকালি রেফারেন্স-কাউন্টেড টাইপ। অ্যাটমিক্স হলো অতিরিক্ত এক ধরনের কনকারেন্সি প্রিমিটিভ যা আমরা এখানে বিস্তারিতভাবে আলোচনা করব না: আরও বিস্তারিত জানার জন্য স্ট্যান্ডার্ড লাইব্রেরি ডকুমেন্টেশন std::sync::atomic দেখুন। এই মুহূর্তে, আপনাকে শুধু জানতে হবে যে অ্যাটমিক্স প্রিমিটিভ টাইপের মতো কাজ করে কিন্তু থ্রেড জুড়ে শেয়ার করার জন্য নিরাপদ।

আপনি তখন ভাবতে পারেন কেন সমস্ত প্রিমিটিভ টাইপ অ্যাটমিক নয় এবং কেন স্ট্যান্ডার্ড লাইব্রেরি টাইপগুলো ডিফল্টরূপে Arc<T> ব্যবহার করার জন্য ইমপ্লিমেন্ট করা হয় না। কারণ হলো থ্রেড সেফটির সাথে একটি পারফরম্যান্স পেনাল্টি আসে যা আপনি শুধুমাত্র যখন সত্যিই প্রয়োজন তখনই দিতে চান। আপনি যদি শুধুমাত্র একটি একক থ্রেডের মধ্যে ভ্যালুগুলোর উপর অপারেশন করেন, তবে আপনার কোড দ্রুত চলতে পারে যদি এটিকে অ্যাটমিক্স যে গ্যারান্টি প্রদান করে তা প্রয়োগ করতে না হয়।

আসুন আমাদের উদাহরণে ফিরে যাই: Arc<T> এবং Rc<T>-এর একই API রয়েছে, তাই আমরা use লাইন, new-এর কল এবং clone-এর কল পরিবর্তন করে আমাদের প্রোগ্রামটি ঠিক করি। তালিকা ১৬-১৫-এর কোডটি অবশেষে কম্পাইল হবে এবং চলবে।

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();

            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

এই কোডটি নিম্নলিখিতটি প্রিন্ট করবে:

Result: 10

আমরা এটা করেছি! আমরা ০ থেকে ১০ পর্যন্ত গণনা করেছি, যা খুব চিত্তাকর্ষক মনে নাও হতে পারে, কিন্তু এটি আমাদের Mutex<T> এবং থ্রেড সেফটি সম্পর্কে অনেক কিছু শিখিয়েছে। আপনি এই প্রোগ্রামের কাঠামোটি শুধু একটি কাউন্টার বাড়ানোর চেয়ে আরও জটিল অপারেশন করার জন্যও ব্যবহার করতে পারেন। এই কৌশলটি ব্যবহার করে, আপনি একটি গণনাকাজকে স্বাধীন অংশে ভাগ করতে পারেন, সেই অংশগুলোকে থ্রেড জুড়ে বিভক্ত করতে পারেন, এবং তারপর প্রতিটি থ্রেডকে তার অংশ দিয়ে চূড়ান্ত ফলাফল আপডেট করার জন্য একটি Mutex<T> ব্যবহার করতে পারেন।

মনে রাখবেন যে আপনি যদি সহজ সাংখ্যিক অপারেশন করেন, তবে স্ট্যান্ডার্ড লাইব্রেরির std::sync::atomic মডিউল দ্বারা প্রদত্ত Mutex<T> টাইপের চেয়ে সহজ টাইপ রয়েছে। এই টাইপগুলো প্রিমিটিভ টাইপগুলোতে নিরাপদ, কনকারেন্ট, অ্যাটমিক অ্যাক্সেস প্রদান করে। আমরা এই উদাহরণের জন্য একটি প্রিমিটিভ টাইপের সাথে Mutex<T> ব্যবহার করতে বেছে নিয়েছি যাতে আমরা Mutex<T> কীভাবে কাজ করে তার উপর মনোযোগ দিতে পারি।

RefCell<T>/Rc<T> এবং Mutex<T>/Arc<T> এর মধ্যে সাদৃশ্য

আপনি হয়তো লক্ষ্য করেছেন যে counter অপরিবর্তনীয় কিন্তু আমরা এর ভিতরের ভ্যালুতে একটি মিউটেবল রেফারেন্স পেতে পারি; এর মানে হলো Mutex<T> ইন্টেরিয়র মিউটেবিলিটি (interior mutability) প্রদান করে, যেমন Cell পরিবার করে। অধ্যায় ১৫-এ আমরা যেভাবে Rc<T>-এর ভিতরের বিষয়বস্তু মিউটেট করার অনুমতি দেওয়ার জন্য RefCell<T> ব্যবহার করেছি, সেভাবেই আমরা Arc<T>-এর ভিতরের বিষয়বস্তু মিউটেট করার জন্য Mutex<T> ব্যবহার করি।

আরেকটি বিষয় লক্ষ্য করার মতো হলো যে আপনি যখন Mutex<T> ব্যবহার করেন তখন Rust আপনাকে সব ধরনের লজিক এরর থেকে রক্ষা করতে পারে না। অধ্যায় ১৫ থেকে মনে করুন যে Rc<T> ব্যবহার করার সাথে রেফারেন্স সাইকেল (reference cycles) তৈরি করার ঝুঁকি ছিল, যেখানে দুটি Rc<T> ভ্যালু একে অপরকে রেফার করে, যা মেমরি লিকের কারণ হয়। একইভাবে, Mutex<T>-এর সাথে ডেডলক (deadlocks) তৈরি করার ঝুঁকি রয়েছে। এটি তখন ঘটে যখন একটি অপারেশনের জন্য দুটি রিসোর্স লক করার প্রয়োজন হয় এবং দুটি থ্রেড প্রত্যেকে একটি করে লক অর্জন করে, যার ফলে তারা একে অপরের জন্য চিরকাল অপেক্ষা করে। আপনি যদি ডেডলকে আগ্রহী হন, তবে একটি Rust প্রোগ্রাম তৈরি করার চেষ্টা করুন যাতে একটি ডেডলক আছে; তারপর যেকোনো ল্যাঙ্গুয়েজে মিউটেক্সের জন্য ডেডলক প্রশমন কৌশল নিয়ে গবেষণা করুন এবং Rust-এ সেগুলো ইমপ্লিমেন্ট করার চেষ্টা করুন। Mutex<T> এবং MutexGuard-এর জন্য স্ট্যান্ডার্ড লাইব্রেরি API ডকুমেন্টেশন দরকারী তথ্য প্রদান করে।

আমরা Send এবং Sync ট্রেইট এবং কীভাবে আমরা কাস্টম টাইপের সাথে সেগুলো ব্যবহার করতে পারি সে সম্পর্কে কথা বলে এই অধ্যায়টি শেষ করব।