Skill

Concurrency এবং Multithreading (কনকারেন্সি এবং মাল্টিথ্রেডিং)

রাস্ট (Rust) - Computer Programming

250

Concurrency (কনকারেন্সি)

কনকারেন্সি (Concurrency) হল একাধিক কাজের সমান্তরালভাবে সম্পাদন করার ধারণা, তবে এটি একসাথে না হয়ে ধাপে ধাপে হতে পারে। অর্থাৎ, একাধিক কাজ একসাথে "চালানো" মনে হলেও বাস্তবে সেগুলি CPU কোরে একে একে চলতে পারে, যেখানে কাজগুলো একে অপরের সাথে শেয়ার করতে পারে। কনকারেন্সির মাধ্যমে প্রোগ্রাম কোডের কার্যকারিতা বাড়ানো যায়, বিশেষ করে যদি এটি বিভিন্ন কাজের মাঝে সম্পদ ভাগ করে।

কনকারেন্সির প্রধান লক্ষ্য হলো একাধিক কাজের মধ্যে সঠিক সমন্বয় করা এবং সেগুলোর মধ্যে বাধা বা ডেটা রেস (data race) এড়ানো।

কনকারেন্সির উদাহরণ:

ধরা যাক, দুটি কাজ একে একে সম্পন্ন করা উচিত, কিন্তু আমরা চাই দুটি কাজ যেন একে অপরের সাথে সমন্বিত হয়ে চলে, যেমন:

  • একটি ওয়েব সার্ভার কাজ করছে, ক্লায়েন্টের রিকোয়েস্ট প্রক্রিয়াকরণ করছে।
  • দ্বিতীয় কাজ হচ্ছে ডেটাবেসে ডাটা আপডেট করা।

কনকারেন্সির মাধ্যমে, আমরা এই দুটি কাজ সমান্তরালভাবে কার্যকর করতে পারি, তবে তা এক সাথে একাধিক থ্রেডে হতে পারে না।

Multithreading (মাল্টিথ্রেডিং)

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

মাল্টিথ্রেডিংয়ের উদাহরণ:

ধরা যাক, আমাদের একটি প্রোগ্রাম আছে যা কয়েকটি পরিসংখ্যানিক হিসাব করে এবং আমরা চাই যে প্রতিটি হিসাব আলাদা থ্রেডে সম্পাদিত হোক। এতে আমাদের কোড দ্রুততম সময়ের মধ্যে কাজ করবে।


Concurrency এবং Multithreading এর মধ্যে পার্থক্য

পার্থক্যConcurrency (কনকারেন্সি)Multithreading (মাল্টিথ্রেডিং)
সংজ্ঞাএকাধিক কাজের সমন্বিত কার্যকরীকরণ।একাধিক থ্রেডের মাধ্যমে একাধিক কাজের একসাথে কার্যকরীকরণ।
কার্যপদ্ধতিএকে একে কাজ চলতে পারে, কিন্তু এদের মধ্যে সঠিক সমন্বয় থাকতে হবে।একাধিক থ্রেডে একসাথে কাজ চলতে পারে, যেখানে CPU কোরের ব্যবহার সম্ভব।
সম্পদ ব্যবস্থাপনাএকাধিক কাজ একটি CPU কোরে সময় ভাগ করে নেয়।একাধিক থ্রেড একাধিক CPU কোরে কাজ করতে পারে।
কার্যকারিতাসাধারনত একাধিক কাজের মধ্যে সমন্বয় করার জন্য ব্যবহৃত হয়, যেমন কাজের দেরি হয়ে গেলে অন্য কাজ চলছে।দ্রুত কাজের জন্য ব্যবহার করা হয়, যেমন একই সময়ে বিভিন্ন কাজ করা।

Rust এ Concurrency এবং Multithreading

রাস্টের মধ্যে কনকারেন্সি এবং মাল্টিথ্রেডিং খুবই শক্তিশালী এবং নিরাপদ উপায়ে কাজ করে। রাস্টের ownership model, borrowing, এবং lifetime সিস্টেম কনকারেন্সি এবং মাল্টিথ্রেডিং কোডের নিরাপত্তা নিশ্চিত করে, বিশেষ করে ডেটা রেস (data race) এড়াতে।

মাল্টিথ্রেডিং রাস্টে:

রাস্টে মাল্টিথ্রেডিং করতে std::thread মডিউল ব্যবহার করা হয়। একাধিক থ্রেড তৈরি এবং তাদের মধ্যে সমন্বয় করা সহজ।

উদাহরণ:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        println!("This is running in a new thread!");
    });

    handle.join().unwrap(); // Main thread waits for the spawned thread to finish
    println!("Main thread is finished.");
}

এখানে, thread::spawn দিয়ে একটি নতুন থ্রেড তৈরি করা হয়েছে যা আলাদা কাজ করছে। join() ব্যবহার করে মূল থ্রেড অপেক্ষা করছে থ্রেডটির শেষ হওয়ার জন্য।

কনকারেন্সি রাস্টে:

রাস্টে কনকারেন্সি হ্যান্ডল করতে Arc (atomic reference counting) এবং Mutex (mutual exclusion) ব্যবহার করা হয়, যাতে একাধিক থ্রেড একই ডেটাতে কাজ করতে পারে কিন্তু সেই ডেটা নিরাপদ থাকে।

উদাহরণ (কনকারেন্সি):

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

fn main() {
    let counter = Arc::new(Mutex::new(0)); // Counter wrapped in Mutex and Arc

    let mut handles = vec![];

    for _ in 0..10 {
        let counter_clone = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter_clone.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

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

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

এখানে, Arc এবং Mutex ব্যবহার করে একাধিক থ্রেডের মধ্যে একটি কাউন্টার শেয়ার করা হয়েছে এবং প্রতিটি থ্রেড নিরাপদে সেই কাউন্টার বৃদ্ধি করছে।


সারাংশ

  • Concurrency (কনকারেন্সি) হল একাধিক কাজের সমন্বিতভাবে কার্যকরীকরণ, যেখানে একসাথে কাজগুলো একে একে চলতে পারে।
  • Multithreading (মাল্টিথ্রেডিং) হল কনকারেন্সি অর্জনের একটি পদ্ধতি, যেখানে একাধিক থ্রেড সমান্তরালভাবে কাজ করতে পারে।
  • রাস্টে কনকারেন্সি এবং মাল্টিথ্রেডিং খুবই শক্তিশালী এবং নিরাপদভাবে ব্যবহার করা যায়, যেখানে ডেটা রেস এড়াতে Arc, Mutex এবং thread মডিউল ব্যবহৃত হয়।

রাস্টের মাল্টিথ্রেডিং ও কনকারেন্সি ব্যবস্থাপনা অন্যান্য ভাষার তুলনায় নিরাপদ এবং কার্যকরী, যা সিস্টেমের পারফরম্যান্স বাড়ানোর পাশাপাশি কোডের নিরাপত্তা নিশ্চিত করে।

Content added By

রাস্টের ownership model কনকারেন্সি বা মাল্টি-থ্রেডিং প্রোগ্রামিংয়ে নিরাপত্তা এবং সঠিকতা নিশ্চিত করার জন্য একটি শক্তিশালী মেকানিজম। মাল্টি-থ্রেডেড পরিবেশে ডেটা রেস (data race) বা অন্যান্য সিঙ্ক্রনাইজেশন সমস্যা সাধারণত ঘটে থাকে, কিন্তু রাস্টের মালিকানা (ownership) এবং বোরোউ (borrowing) সিস্টেমের মাধ্যমে এই সমস্যাগুলি কার্যকরভাবে এড়ানো সম্ভব। রাস্ট তার মালিকানা মডেলের মাধ্যমে কনকারেন্সির ক্ষেত্রে ডেটা রেস এবং ডেডলক (deadlock) সমস্যা মোকাবেলা করতে সহায়তা করে।


Ownership Model এবং Concurrency

রাস্টের ownership model কনকারেন্সির ক্ষেত্রে মূলত দুটি উপায়ে কাজ করে:

  1. Ownership Transfer (মালিকানা স্থানান্তর)
  2. Borrowing (বোরোউ)

রাস্টে, যদি একাধিক থ্রেড একসাথে ডেটা অ্যাক্সেস করার চেষ্টা করে, তবে মালিকানা স্থানান্তর বা বোরোউ ব্যবহারের মাধ্যমে নিশ্চিত করা হয় যে, ডেটার উপর একাধিক থ্রেডের অ্যাক্সেস হবে না, যা data races প্রতিরোধে সহায়ক। মালিকানা স্থানান্তর এবং বোরোউ ব্যবহারের মাধ্যমে ডেটার অ্যাক্সেস সুরক্ষিত হয় এবং একসাথে একাধিক থ্রেডের মধ্যে নিরাপদভাবে কাজ করা যায়।


Ownership Transfer (মালিকানা স্থানান্তর)

রাস্টে, মালিকানার স্থানান্তর একেবারে সরল এবং নিরাপদ। যখন আপনি একটি থ্রেডে ডেটা স্থানান্তর করেন, তখন ওই থ্রেডের বাইরে সেই ডেটা আর অ্যাক্সেসযোগ্য থাকে না। অর্থাৎ, মালিকানা এক থ্রেড থেকে অন্য থ্রেডে স্থানান্তরিত হলে, পূর্ববর্তী থ্রেডে সেই ডেটার ব্যবহার বন্ধ হয়ে যায়।

উদাহরণ:

use std::thread;

fn main() {
    let s = String::from("Hello, Rust!");

    let handle = thread::spawn(move || {
        println!("{}", s);  // সঠিক, কারণ `s` থ্রেডে move করা হয়েছে
    });

    handle.join().unwrap();  // থ্রেডের সম্পূর্ণতা নিশ্চিত করা
}

এখানে, s ভেরিয়েবলটি move কিওয়ার্ড ব্যবহার করে নতুন থ্রেডে স্থানান্তরিত হয়েছে। এর ফলে, মূল থ্রেডে s আর অ্যাক্সেসযোগ্য থাকে না এবং কনকারেন্ট থ্রেডে মালিকানা স্থানান্তরিত হয়।


Borrowing (বোরোউ)

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

Immutable Borrowing (অপরিবর্তনীয় বোরোউ):

আপনি যদি ডেটার মালিকানা না নিয়ে শুধুমাত্র রেফারেন্স ব্যবহার করেন, তবে একাধিক থ্রেড একসাথে ওই রেফারেন্সের মাধ্যমে ডেটাকে অ্যাক্সেস করতে পারে। কিন্তু, যদি কোনো থ্রেড মিউটেবল রেফারেন্স ব্যবহার করতে চায়, তবে অন্য কোনো থ্রেড ঐ ডেটাকে পরিবর্তন করতে পারবে না।

উদাহরণ:

use std::thread;

fn main() {
    let s = String::from("Hello, Rust!");

    let handle = thread::spawn(|| {
        println!("{}", s);  // Immutable borrow
    });

    handle.join().unwrap();  // থ্রেডের সম্পূর্ণতা নিশ্চিত করা
}

এখানে s একটি immutable borrow হিসাবে শেয়ার করা হয়েছে এবং একাধিক থ্রেড ঐ ডেটা ব্যবহার করতে পারবে।

Mutable Borrowing (মিউটেবল বোরোউ):

মিউটেবল বোরোউ শুধুমাত্র একটি থ্রেডকে ডেটার পরিবর্তন করার অনুমতি দেয়। একাধিক থ্রেড একই সময়ে ডেটার উপর মিউটেবল রেফারেন্স ব্যবহার করতে পারে না, যা data race এড়ানোর জন্য গুরুত্বপূর্ণ।

উদাহরণ:

use std::thread;

fn main() {
    let mut s = String::from("Hello, Rust!");

    let handle = thread::spawn(move || {
        s.push_str(" I'm learning Rust!");  // Mutable borrow
    });

    handle.join().unwrap();  // থ্রেডের সম্পূর্ণতা নিশ্চিত করা
    println!("{}", s);  // এখানে সমস্যা হবে, কারণ s মিউটেবলভাবে মুভ করা হয়েছে
}

এখানে, s ভেরিয়েবলটি মিউটেবল বোরোউ করার জন্য মুভ করা হয়েছে। একাধিক থ্রেড মিউটেবল রেফারেন্স নিয়ে ডেটাকে পরিবর্তন করার চেষ্টা করলে তা নিরাপদ হবে না।


Concurrency এবং Rust এর Ownership Model এর সুবিধা

রাস্টের মালিকানা মডেল কনকারেন্সির ক্ষেত্রে যে সুবিধাগুলি প্রদান করে তা হলো:

  1. ডেটা রেস প্রিভেনশন: মালিকানা স্থানান্তর এবং রেফারেন্স সিস্টেম ব্যবহার করে ডেটা রেস (data races) প্রতিরোধ করা যায়। যদি একাধিক থ্রেড একই ডেটা ব্যবহার করার চেষ্টা করে, রাস্ট এটি কম্পাইল টাইম এ ধরা পড়ে, তাই রানটাইম সমস্যা হয় না।
  2. পারফরম্যান্স: মালিকানা স্থানান্তর এবং বোরোউ সিস্টেমের মাধ্যমে রাস্ট কম পারফরম্যান্স খরচে কনকারেন্ট প্রোগ্রামিং নিশ্চিত করতে পারে।
  3. থ্রেড সেফটি: মালিকানা মডেল সিস্টেমের মধ্যে নিশ্চিত করে যে, একাধিক থ্রেডের মধ্যে একই ডেটা নিরাপদে শেয়ার এবং পরিবর্তন করা সম্ভব। এতে কোনো ডেডলক বা রেস কন্ডিশন সৃষ্টি হয় না।

সারাংশ

রাস্টের ownership model কনকারেন্সির ক্ষেত্রে অত্যন্ত শক্তিশালী এবং নিরাপদ। মালিকানা স্থানান্তর এবং বোরোউ সিস্টেম কনকারেন্ট থ্রেডের মধ্যে সঠিক ডেটা শেয়ার এবং ব্যবহার নিশ্চিত করে, ডেটা রেস, ডেডলক, এবং অন্যান্য সিঙ্ক্রনাইজেশন সমস্যা প্রতিরোধ করে। রাস্টের মালিকানা এবং থ্রেড সেফটি কনকারেন্সি প্রোগ্রামিংকে আরও নির্ভরযোগ্য এবং কার্যকর করে তোলে।

Content added By

Threads (থ্রেডস)

রাস্টে, threads একটি প্রক্রিয়ার (process) মধ্যে একাধিক একক কার্যকলাপ (execution units) তৈরি করতে সক্ষম, যা একে মাল্টি-থ্রেডেড (multi-threaded) অ্যাপ্লিকেশন তৈরি করার অনুমতি দেয়। এটি প্যারালাল প্রসেসিং এবং কনকারেন্ট প্রোগ্রামিংয়ের জন্য ব্যবহৃত হয়, যেখানে একাধিক কাজ একসাথে চলতে পারে।

Threads in Rust

রাস্টে থ্রেডস ব্যবহারের জন্য std::thread মডিউল ব্যবহার করা হয়। থ্রেড তৈরি করার জন্য thread::spawn ফাংশন ব্যবহার করা হয়, যা একটি নতুন থ্রেড শুরু করে এবং একটি ক্লোজার অথবা ফাংশন দেয় যা সেই থ্রেডে চলবে।

উদাহরণ:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..5 {
            println!("Child thread: {}", i);
        }
    });

    for i in 1..5 {
        println!("Main thread: {}", i);
    }

    handle.join().unwrap(); // মূল থ্রেড অপেক্ষা করবে যে চাইল্ড থ্রেড কাজ শেষ করবে
}

এখানে, thread::spawn একটি নতুন থ্রেড তৈরি করে এবং সেই থ্রেডে কোড চলতে থাকে। join() ফাংশন ব্যবহার করে মূল থ্রেড (main thread) অপেক্ষা করবে যে চাইল্ড থ্রেড কাজ শেষ করবে। যদি join() ব্যবহার না করা হয়, তাহলে মূল থ্রেড তাড়াতাড়ি শেষ হয়ে যেতে পারে, যার ফলে চাইল্ড থ্রেডের কাজ সমাপ্ত হওয়ার আগে প্রোগ্রামটি শেষ হয়ে যেতে পারে।

Multithreading and Data Sharing

রাস্টের থ্রেড ব্যবস্থাপনা ownership এবং borrowing মডেল অনুসরণ করে, যা মেমোরি সেফটি নিশ্চিত করে। থ্রেডগুলোর মধ্যে ডেটা শেয়ার করার জন্য রেফারেন্স পাস করা যায়, তবে সুরক্ষা নিশ্চিত করতে Mutex এবং Arc ব্যবহার করা হয়।


Message Passing (মেসেজ পাসিং)

রাস্টে থ্রেডের মধ্যে যোগাযোগ বা ডেটা শেয়ার করার একটি সাধারণ পদ্ধতি হল Message Passing। এটি থ্রেডগুলোর মধ্যে নিরাপদভাবে তথ্য আদান-প্রদান করার একটি পদ্ধতি, যেখানে এক থ্রেড অন্য থ্রেডকে মেসেজ পাঠায় এবং থ্রেডগুলোর মধ্যে ডেটা শেয়ারিং সুরক্ষিত থাকে।

রাস্টে মেসেজ পাসিং এর জন্য channels ব্যবহৃত হয়। Channels এক ধরনের FIFO (First-In-First-Out) কিউ প্রদান করে, যা এক থ্রেডের পাঠানো মেসেজ অন্য থ্রেড গ্রহণ করতে পারে।

Channels in Rust

রাস্টে channels তৈরি করা যায় std::sync::mpsc (multiple producer, single consumer) মডিউল ব্যবহার করে। mpsc::channel ফাংশন ব্যবহার করে একটি চ্যানেল তৈরি করা হয়, যা একটি sender এবং receiver প্রদান করে।

উদাহরণ:

use std::thread;
use std::sync::mpsc;

fn main() {
    // চ্যানেল তৈরি করা
    let (tx, rx) = mpsc::channel();

    // একটি নতুন থ্রেড তৈরি করা
    thread::spawn(move || {
        let message = String::from("Hello from thread!");
        tx.send(message).unwrap(); // মেসেজ পাঠানো
    });

    // মেসেজ গ্রহণ করা
    let received_message = rx.recv().unwrap();
    println!("Received: {}", received_message);
}

এখানে:

  • mpsc::channel() একটি চ্যানেল তৈরি করে, যা একটি tx (sender) এবং rx (receiver) প্রদান করে।
  • tx.send(message) মেসেজ পাঠানোর জন্য ব্যবহার করা হয়।
  • rx.recv() মেসেজ গ্রহণ করার জন্য ব্যবহার করা হয়।

মেসেজ পাসিং একটি গুরুত্বপূর্ণ পদ্ধতি, কারণ এটি থ্রেডের মধ্যে ডেটা শেয়ার করার সময় ownership নিয়মগুলো পালন করে, এবং ডেটা রেস (data races) এবং মেমোরি নিরাপত্তা সমস্যা এড়ায়।


Threads এবং Message Passing এর মধ্যে সম্পর্ক

  • Threads একাধিক কার্যকলাপ (execution units) তৈরি করতে সক্ষম, যা মাল্টি-থ্রেডেড প্রোগ্রামিংয়ের জন্য উপকারী।
  • Message Passing থ্রেডগুলোর মধ্যে যোগাযোগের একটি নিরাপদ পদ্ধতি যেখানে ডেটা এক থ্রেড থেকে অন্য থ্রেডে মেসেজের মাধ্যমে পাস করা হয়।
  • মেসেজ পাসিং থ্রেডগুলোর মধ্যে ownership নিয়ম মেনে চলতে সহায়তা করে, এবং data races এবং memory safety সমস্যা এড়ানোর জন্য গুরুত্বপূর্ণ।

Mutex এবং Arc: Thread Safety with Shared Data

যখন একাধিক থ্রেড একযোগভাবে একে অপরের সাথে কাজ করে, তখন mutex এবং Arc ব্যবহার করা হয় ডেটা সুরক্ষিত রাখতে।

  • Mutex (Mutual Exclusion): এটি একটি সিঙ্ক্রোনাইজেশন প্রিমিটিভ, যা একে একে থ্রেডগুলিকে একটি ডেটা সম্পদ অ্যাক্সেস করতে দেয়।
  • Arc (Atomic Reference Counted): এটি থ্রেডের মধ্যে শেয়ারযোগ্য ডেটা ব্যবস্থাপনার জন্য ব্যবহার করা হয়।

উদাহরণ:

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

এখানে:

  • Arc ডেটা শেয়ার করার জন্য ব্যবহৃত হচ্ছে এবং এটি বিভিন্ন থ্রেডে ব্যবহার করার জন্য সুরক্ষিত।
  • Mutex ব্যবহৃত হচ্ছে ডেটা অ্যাক্সেসের সময় থ্রেডগুলোর মধ্যে একে একে অ্যাক্সেস নিশ্চিত করতে।

সারাংশ

  • Threads থ্রেডের মাধ্যমে একাধিক কার্যকলাপ চালানো সম্ভব করে এবং একাধিক কাজ একসাথে সম্পাদন করতে সাহায্য করে।
  • Message Passing থ্রেডগুলোর মধ্যে নিরাপদভাবে তথ্য আদান-প্রদান করার একটি পদ্ধতি, যা ডেটা রেস এবং মেমোরি সেফটি সমস্যাগুলি এড়াতে সহায়ক।
  • Mutex এবং Arc ব্যবহার করে শেয়ার করা ডেটা নিরাপদভাবে একাধিক থ্রেডের মধ্যে অ্যাক্সেস করা যেতে পারে।
Content added By

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

Shared State Concurrency হল একটি কনকারেন্ট প্রোগ্রামিং মডেল যেখানে একাধিক থ্রেড একসাথে একটি স্টেট বা ভেরিয়েবলের সাথে কাজ করে। সাধারণত, যখন একাধিক থ্রেড একটি শেয়ারড ভেরিয়েবলের মান পরিবর্তন করতে চেষ্টা করে, তখন ডেটা রেস (data race) এবং অন্যান্য সমস্যা হতে পারে। এসব সমস্যা এড়াতে সিঙ্ক্রোনাইজেশন ব্যবস্থার প্রয়োজন হয়, যাতে নিশ্চিত করা যায় যে শুধুমাত্র একটি থ্রেড একবারে শেয়ারড ডেটাতে কাজ করছে।

রাস্টে শেয়ারড স্টেট কনকারেন্সি সাধারণত Mutex এবং Arc ব্যবহার করে করা হয়। এগুলি সঠিকভাবে থ্রেডের মধ্যে শেয়ারড ডেটা সিঙ্ক্রোনাইজ করার জন্য ব্যবহৃত হয়।


Mutex (মিউটেক্স)

Mutex (Mutual Exclusion) একটি সিঙ্ক্রোনাইজেশন প্রাইমিটিভ যা একাধিক থ্রেডের মধ্যে একটি শেয়ারড রিসোর্সের অ্যাক্সেস নিয়ন্ত্রণ করতে ব্যবহৃত হয়। Mutex নিশ্চিত করে যে শুধুমাত্র একটি থ্রেড একবারে শেয়ারড ডেটার সাথে কাজ করতে পারে। যদি এক থ্রেড মিউটেক্স লক করে, অন্য কোনো থ্রেড সেই রিসোর্সে প্রবেশ করতে পারবে না যতক্ষণ না মিউটেক্সটি আনলক হয়।

Mutex ব্যবহার:

রাস্টে Mutex একটি জেনেরিক টাইপ হিসেবে থাকে, এবং এটি std::sync::Mutex মডিউলে থাকে। মিউটেক্স ব্যবহার করার জন্য আপনাকে প্রথমে এটি লক করতে হবে, এবং লক মুক্তির জন্য এটি unlock করার প্রয়োজন নেই, কারণ মিউটেক্সের লকগুলো ব্লকিং হয়ে থাকে যতক্ষণ না তারা বের হয়ে আসে।

উদাহরণ:

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);  // Arc ক্লোন করা হচ্ছে, যাতে এটি শেয়ার করা যায়
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap(); // Mutex লক করা হচ্ছে
            *num += 1;  // ডেটা পরিবর্তন করা হচ্ছে
        });
        handles.push(handle);
    }

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

    println!("Result: {}", *counter.lock().unwrap()); // মিউটেক্স আনলক করা এবং ফলাফল প্রিন্ট করা
}

এখানে, Mutex ব্যবহৃত হয়েছে counter ভেরিয়েবলকে রক্ষা করতে, যাতে একাধিক থ্রেড একে একে এই ভেরিয়েবলটি পরিবর্তন করতে পারে। Arc (Atomic Reference Counting) ব্যবহার করা হয়েছে কারণ Mutex একক মালিকানায় থাকে, এবং শেয়ারড মালিকানা নিশ্চিত করার জন্য Arc ব্যবহার করা হয়।


Arc (Atomic Reference Counting)

Arc (Atomic Reference Counting) একটি স্মার্ট পয়েন্টার যা শেয়ারড মালিকানার সিস্টেমের জন্য ব্যবহৃত হয়। এটি থ্রেডের মধ্যে ডেটা শেয়ার করতে ব্যবহৃত হয় যেখানে ডেটা একাধিক থ্রেডের মধ্যে নিরাপদভাবে অ্যাক্সেস করা দরকার। Arc গ্যারান্টি দেয় যে যখন পর্যন্ত সমস্ত থ্রেড ডেটার রেফারেন্সের মালিক থাকবে, ডেটা ডিলিট হবে না।

Arc সাধারণত শেয়ারড স্টেট কনকারেন্সির সাথে ব্যবহৃত হয় এবং এটি মিউটেক্স বা অন্য কোনো সিঙ্ক্রোনাইজেশন প্রাইমিটিভের সাথে কাজ করতে পারে।

Arc এর ব্যবহার:

Arc স্মার্ট পয়েন্টার হিসেবে ডেটাকে শেয়ার করে এবং থ্রেডের মধ্যে এটি সুরক্ষিতভাবে একাধিকবার ব্যবহার করা যায়।

উদাহরণ:

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

fn main() {
    let counter = Arc::new(Mutex::new(0)); // Arc এবং Mutex ব্যবহার করে শেয়ারড ডেটা তৈরি

    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);  // Arc ক্লোন করা হচ্ছে
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap(); // Mutex লক
            *num += 1; // ডেটা পরিবর্তন
        });
        handles.push(handle);
    }

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

    println!("Result: {}", *counter.lock().unwrap()); // শেয়ারড ডেটার চূড়ান্ত মান
}

এখানে, Arc ডেটার মালিকানা একাধিক থ্রেডের মধ্যে শেয়ার করে, এবং Mutex থ্রেডের মধ্যে সিঙ্ক্রোনাইজেশন নিশ্চিত করে। Arc এর মাধ্যমে ডেটার মালিকানা একাধিক থ্রেডের মধ্যে ভাগ করা হলেও, এটি এখনও নিরাপদে সিঙ্ক্রোনাইজ করা হয়।


Mutex এবং Arc এর সমন্বয়

রাস্টে, যখন একাধিক থ্রেড একটি শেয়ারড ডেটাতে কাজ করতে পারে, তখন Arc এবং Mutex একসাথে ব্যবহৃত হয়। Arc স্মার্ট পয়েন্টার ডেটাকে শেয়ার করার জন্য ব্যবহৃত হয়, এবং Mutex ডেটাকে একসাথে একমাত্র থ্রেডের মাধ্যমে অ্যাক্সেস করার জন্য সিঙ্ক্রোনাইজেশন করে।

  • Arc: শেয়ারড মালিকানা এবং থ্রেডের মধ্যে ডেটা শেয়ার করতে ব্যবহৃত হয়।
  • Mutex: শেয়ারড ডেটার উপর একাধিক থ্রেডের অ্যাক্সেস নিয়ন্ত্রণ করতে ব্যবহৃত হয়।

সমন্বয়ের উদাহরণ:

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

fn main() {
    let data = Arc::new(Mutex::new(vec![1, 2, 3])); // Arc এবং Mutex ব্যবহার করে শেয়ারড ডেটা

    let mut handles = vec![];

    for _ in 0..10 {
        let data = Arc::clone(&data);  // Arc ক্লোন
        let handle = thread::spawn(move || {
            let mut data = data.lock().unwrap(); // Mutex লক
            data.push(1); // ডেটা পরিবর্তন
        });
        handles.push(handle);
    }

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

    println!("Final data: {:?}", *data.lock().unwrap()); // শেয়ারড ডেটার চূড়ান্ত অবস্থা
}

এখানে Arc এবং Mutex একসাথে ব্যবহৃত হয়েছে, যেখানে শেয়ারড ডেটার উপর একাধিক থ্রেড কাজ করছে এবং প্রতিটি থ্রেড নিশ্চিত করছে যে শুধুমাত্র একটি থ্রেড একবারে ডেটা অ্যাক্সেস করতে পারবে।


সারাংশ

রাস্টে Shared State Concurrency পরিচালনা করার জন্য Mutex এবং Arc একটি শক্তিশালী কনকারেন্ট প্রোগ্রামিং মডেল তৈরি করে। Mutex ব্যবহার করা হয় শেয়ারড ডেটার একযোগ ব্যবহারের জন্য সিঙ্ক্রোনাইজেশন নিশ্চিত করতে, এবং Arc ব্যবহার করা হয় ডেটাকে একাধিক থ্রেডের মধ্যে শেয়ার করার জন্য। একসাথে, এই দুটি প্রাইমিটিভ শেয়ারড স্টেট কনকারেন্সি সুরক্ষিত এবং কার্যকরীভাবে পরিচালনা করতে সহায়তা করে।

Content added By

Asynchronous Programming (অ্যাসিনক্রোনাস প্রোগ্রামিং)

Asynchronous programming (অ্যাসিনক্রোনাস প্রোগ্রামিং) এমন একটি প্রোগ্রামিং প্যাটার্ন যেখানে কাজের একটি অংশ চালানো হয় এবং অন্য অংশগুলো একই সময়ে চালু থাকে, না যে কাজের সমাপ্তি পর্যন্ত অপেক্ষা করা হয়। এতে প্রোগ্রামটির কার্যক্ষমতা বৃদ্ধি পায়, কারণ যখন এক কার্যকলাপ অন্য এক কার্যকলাপের জন্য অপেক্ষা করে (যেমন ডাটাবেস কল বা নেটওয়ার্ক রিকুয়েস্ট), তখন এই সময়ের মধ্যে অন্য কাজ করা যায়।

এটি বিশেষভাবে I/O-bound (ইনপুট/আউটপুট সম্পর্কিত কাজ) অ্যাপ্লিকেশনগুলির জন্য কার্যকরী, যেখানে প্রোগ্রামটি অনেক সময় নেটওয়ার্ক রিকুয়েস্ট বা ডিস্ক অপারেশনগুলির জন্য অপেক্ষা করে।

Rust এ Asynchronous Programming

রাস্টে অ্যাসিনক্রোনাস প্রোগ্রামিং async এবং await কিওয়ার্ড ব্যবহার করে পরিচালিত হয়। রাস্টের অ্যাসিনক্রোনাস প্রোগ্রামিং সাধারণত async ফাংশন ডিফাইন করা হয় এবং এই ফাংশনগুলিকে অন্য ফাংশন থেকে কল করার জন্য await কিওয়ার্ড ব্যবহৃত হয়।

২.১ async ফাংশন

রাস্টে, async কিওয়ার্ড ফাংশনের আগে ব্যবহার করে একটি অ্যাসিনক্রোনাস ফাংশন তৈরি করা হয়। অ্যাসিনক্রোনাস ফাংশনটি একটি Future রিটার্ন করে, যা আসলে ভবিষ্যতে কোনো মান ফেরত দেওয়ার প্রতিশ্রুতি দেয়।

async fn fetch_data() -> String {
    "Data fetched".to_string()
}

এখানে fetch_data একটি অ্যাসিনক্রোনাস ফাংশন যা ভবিষ্যতে "Data fetched" স্ট্রিং ফেরত দেওয়ার প্রতিশ্রুতি দেয়।

২.২ await কিওয়ার্ড

যখন একটি অ্যাসিনক্রোনাস ফাংশন কল করা হয়, তখন আমরা await কিওয়ার্ড ব্যবহার করি, যার মাধ্যমে ফাংশনের ফলাফল পাওয়ার জন্য অপেক্ষা করা হয়।

async fn fetch_data() -> String {
    "Data fetched".to_string()
}

async fn main() {
    let result = fetch_data().await;
    println!("{}", result);
}

এখানে fetch_data().await কল করার মাধ্যমে আমরা অ্যাসিনক্রোনাস ফাংশনটি থেকে ফলাফল অপেক্ষা করছি। await কিওয়ার্ড কেবলমাত্র অ্যাসিনক্রোনাস ফাংশনের ভিতরে ব্যবহার করা যায়।


Asynchronous Programming এর প্রয়োগ

রাস্টে অ্যাসিনক্রোনাস প্রোগ্রামিং সাধারণত ফাইল সিস্টেম অপারেশন, নেটওয়ার্ক রিকুয়েস্ট, এবং দীর্ঘ সময় স্থায়ী প্রসেসিং এর মতো কাজগুলোতে ব্যবহৃত হয়, যাতে প্রোগ্রামটি একসময়েকেই একাধিক কাজ সম্পাদন করতে সক্ষম হয়।

৩.১ নেটওয়ার্ক রিকুয়েস্ট (Networking Requests)

নেটওয়ার্ক রিকুয়েস্টের জন্য সাধারণত অ্যাসিনক্রোনাস ফাংশন ব্যবহৃত হয়, কারণ এই রিকুয়েস্টের জন্য অপেক্ষা করার সময় প্রোগ্রামকে অন্য কাজ করতে সক্ষম হওয়া দরকার।

উদাহরণ:

use reqwest;

async fn fetch_website() -> Result<String, reqwest::Error> {
    let response = reqwest::get("https://www.rust-lang.org").await?;
    let body = response.text().await?;
    Ok(body)
}

#[tokio::main]
async fn main() {
    match fetch_website().await {
        Ok(body) => println!("Website content fetched successfully."),
        Err(e) => println!("Error fetching website: {}", e),
    }
}

এখানে, fetch_website একটি অ্যাসিনক্রোনাস ফাংশন যা HTTP রিকুয়েস্ট করে এবং রেসপন্সের জন্য অপেক্ষা করে।

৩.২ ডাটাবেস অপারেশন (Database Operations)

অ্যাসিনক্রোনাস প্রোগ্রামিং ডাটাবেস অপারেশনেও ব্যবহৃত হতে পারে। যখন ডাটাবেসে একটি দীর্ঘ প্রক্রিয়া চলছে, তখন প্রোগ্রামটি অ্যাসিনক্রোনাসভাবে ডাটাবেস রিকুয়েস্ট করে এবং এর পরবর্তী কাজগুলি অব্যাহত রাখে।

উদাহরণ:

use tokio_postgres::{NoTls, Error};

async fn query_database() -> Result<String, Error> {
    let (client, connection) = tokio_postgres::connect("host=localhost user=postgres", NoTls).await?;
    
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("Connection error: {}", e);
        }
    });

    let rows = client.query("SELECT name FROM users", &[]).await?;
    let result = rows.iter().map(|row| row.get::<_, String>(0)).collect::<Vec<_>>().join(", ");
    
    Ok(result)
}

#[tokio::main]
async fn main() {
    match query_database().await {
        Ok(names) => println!("Fetched user names: {}", names),
        Err(e) => eprintln!("Error fetching data: {}", e),
    }
}

এখানে, query_database একটি অ্যাসিনক্রোনাস ডাটাবেস কুয়েরি ফাংশন যা অ্যাসিনক্রোনাসভাবে ডাটাবেস রিকুয়েস্ট করে এবং ফলাফল ফেরত দেয়।

৩.৩ UI অ্যাপ্লিকেশন (UI Applications)

অ্যাসিনক্রোনাস প্রোগ্রামিংকে UI অ্যাপ্লিকেশন বা গেম ডেভেলপমেন্ট-এ ব্যবহার করা হয়, যেখানে ইভেন্টগুলি একে অপরের সাথে সম্পাদিত হতে থাকে। এই ক্ষেত্রেও অ্যাসিনক্রোনাস কোড ব্যবহার করে UI থ্রেড ব্লক না হয়ে একাধিক কার্যকলাপ একসাথে সম্পন্ন হতে পারে।


async/await এর সুবিধা

  • পারফরম্যান্স উন্নতি: অ্যাসিনক্রোনাস প্রোগ্রামিং নেটওয়ার্ক, I/O বা অন্যান্য দীর্ঘ-সময়ের কাজগুলির জন্য পারফরম্যান্সে উন্নতি ঘটায়, কারণ একে একে একাধিক কাজ পরিচালিত হতে পারে।
  • সাদৃশ্যপূর্ণ কোড: অ্যাসিনক্রোনাস কোড সাধারণত সিঙ্ক্রোনাস কোডের মতোই দেখতে হয়, কারণ আমরা await ব্যবহার করি যা বুঝতে সহজ।
  • এনার্জি সঞ্চয়: অ্যাসিনক্রোনাস কোড হালকা কাজের জন্য থ্রেড ব্লক না হয়ে প্রোগ্রাম চালিয়ে যেতে সাহায্য করে, যা কম শক্তি খরচে কাজ সম্পাদন করতে সহায়তা করে।

সারাংশ

রাস্টে async এবং await কিওয়ার্ডগুলি অ্যাসিনক্রোনাস প্রোগ্রামিংয়ের মূল অংশ। অ্যাসিনক্রোনাস প্রোগ্রামিংয়ের মাধ্যমে বিভিন্ন I/O বা নেটওয়ার্ক রিকুয়েস্টের জন্য অপেক্ষা না করে প্রোগ্রাম একাধিক কাজ একসাথে করতে পারে। এটি নেটওয়ার্ক রিকুয়েস্ট, ডাটাবেস কুয়েরি, UI অ্যাপ্লিকেশন এবং অন্যান্য ক্ষেত্রে কার্যকরীভাবে ব্যবহৃত হয়। async ফাংশন Future রিটার্ন করে, এবং await কিওয়ার্ড ব্যবহার করে অ্যাসিনক্রোনাস কাজের ফলাফল গ্রহণ করা হয়।

Content added By
Promotion

Are you sure to start over?

Loading...