পাঠ ১৫ · ৫৬-এর মধ্যে · মডিউল ৪
Home / Courses / Operating Systems (OS) / মাল্টিথ্রেডিং মডেল

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

Multithreading models
৭ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ইউজার-লেভেল থ্রেড ও কার্নেল-লেভেল থ্রেডের মধ্যে পার্থক্য
  • Many-to-One মডেল এবং এর ব্লকিং সমস্যা
  • One-to-One মডেল এবং এটি কেন আধুনিক OS-এর প্রধান পছন্দ
  • Many-to-Many মডেল একটি তাত্ত্বিক মধ্যম পথ হিসেবে
  • Python দিয়ে তিনটি মডেলের একটি পাশাপাশি তুলনামূলক টেবিল তৈরি করা

১ · ইউজার থ্রেড বনাম কার্নেল থ্রেড

L14-এ আমরা দেখেছি থ্রেড কী। কিন্তু থ্রেড দুই স্তরে থাকতে পারে — ইউজার থ্রেড (User-level Thread)User-level Threadএকটি ইউজার-স্পেস লাইব্রেরি দ্বারা তৈরি ও পরিচালিত থ্রেড, যা OS কার্নেল সরাসরি দেখতে পায় না। তৈরি হয় ও পরিচালিত হয় একটি লাইব্রেরির মাধ্যমে, যা OS কার্নেলের নাগালের বাইরে — কার্নেল এদের অস্তিত্ব সম্পর্কে কিছুই জানে না। বিপরীতে কার্নেল থ্রেড (Kernel-level Thread)Kernel-level ThreadOS কার্নেল সরাসরি ব্যবস্থাপনা ও শিডিউল করে এমন প্রকৃত থ্রেড — এটিই আসল শিডিউলযোগ্য একক। হলো সেই প্রকৃত একক যা কার্নেল নিজে শিডিউল করে ও CPU-তে বসায়। প্রশ্ন হলো — অনেকগুলো ইউজার থ্রেডকে কীভাবে কার্নেল থ্রেডের সাথে ম্যাপ করা হবে? এর তিনটি প্রধান মডেল আছে।

২ · Many-to-One

অনেক ইউজার থ্রেড একটি মাত্র কার্নেল থ্রেডে ম্যাপ হয় — থ্রেড ব্যবস্থাপনা দ্রুত, কারণ কার্নেলের কোনো সম্পৃক্ততাই লাগে না (সব সুইচিং ইউজার-স্পেস লাইব্রেরির ভেতরেই ঘটে)। কিন্তু একটি গুরুতর সীমাবদ্ধতা — যদি একটি ইউজার থ্রেড I/O-তে ব্লক করে, পুরো প্রসেসই ব্লক হয়ে যায়, কারণ কার্নেল আসলে মাত্র একটি থ্রেডই দেখে।

৩ · One-to-One

প্রতিটি ইউজার থ্রেডের নিজস্ব একটি কার্নেল থ্রেড থাকে — ভালো concurrency (একটি থ্রেড ব্লক করলে অন্যগুলো আটকায় না) এবং একাধিক CPU কোরে (L13) সত্যিকারের প্যারালালিজম সম্ভব হয়। খরচ — একটি থ্রেড তৈরি করতে একটি সংশ্লিষ্ট কার্নেল থ্রেডও তৈরি করতে হয় (বেশি ওভারহেড)। এটিই আধুনিক প্রায় সব OS (Linux, Windows) আসলে ব্যবহার করে।

৪ · Many-to-Many

অনেক ইউজার থ্রেডকে অপেক্ষাকৃত কম-বা-সমান সংখ্যক কার্নেল থ্রেডে মাল্টিপ্লেক্স করা হয় — Many-to-One-এর নমনীয়তা এবং One-to-One-এর ভালো concurrency-এর মাঝামাঝি একটি ভারসাম্য অর্জনের লক্ষ্যে। তাত্ত্বিকভাবে এটি আদর্শ, কিন্তু বাস্তবায়ন জটিল বলে আজকের বাস্তব সিস্টেমে One-to-One-এর তুলনায় কম ব্যবহৃত হয়।

Many-to-One ১টি কার্নেল থ্রেড One-to-One ৩টি কার্নেল থ্রেড Many-to-Many ২টি কার্নেল থ্রেড উপরের সারি = ইউজার থ্রেড, নিচের সারি = কার্নেল থ্রেড
তিনটি মডেলেই ইউজার থ্রেড সংখ্যা একই (৩টি), কিন্তু কার্নেল থ্রেডে ম্যাপিং ভিন্ন।
কেন One-to-One জিতে গেছে

আধুনিক হার্ডওয়্যারে একাধিক CPU কোর থাকাই স্বাভাবিক (L13) — Many-to-One-এর "একটি থ্রেড ব্লক করলে সব আটকে যায়" সমস্যা এবং একাধিক কোরে সত্যিকারের প্যারালালিজম করতে না পারার সীমাবদ্ধতা আজকের হার্ডওয়্যারে গ্রহণযোগ্য নয়। তাই One-to-One-এর বাড়তি ওভারহেড মেনে নিয়েও আধুনিক OS এটিকেই প্রধান মডেল হিসেবে বেছে নিয়েছে।

৫ · তিনটি মডেলের তুলনা

নিচের কোড সেলে তিনটি মডেলকে ব্লকিং আচরণ, সত্যিকারের প্যারালালিজম সম্ভব কি না, এবং কার্নেল ওভারহেড — এই তিনটি মাপকাঠিতে তুলনা করা হয়েছে।

Python
# মাল্টিথ্রেডিং মডেল তুলনা -- toy in-memory ডেটা, real threads নয়
models = {
    "Many-to-One": {
        "blocking_behavior": "১টি ইউজার থ্রেড ব্লক করলে পুরো প্রসেস ব্লক হয় (কার্নেল একটিই থ্রেড দেখে)",
        "true_parallelism_possible": "না -- একই সময়ে মাত্র ১টি কার্নেল থ্রেড চলতে পারে",
        "kernel_overhead": "সবচেয়ে কম -- থ্রেড ব্যবস্থাপনা সম্পূর্ণ ইউজার-স্পেসে",
    },
    "One-to-One": {
        "blocking_behavior": "১টি থ্রেড ব্লক করলে অন্যগুলো চলতে থাকে (প্রতিটির নিজস্ব কার্নেল থ্রেড)",
        "true_parallelism_possible": "হ্যাঁ -- একাধিক CPU কোরে একই সময়ে চলতে পারে",
        "kernel_overhead": "সবচেয়ে বেশি -- প্রতিটি থ্রেডের জন্যই একটি কার্নেল থ্রেড তৈরি",
    },
    "Many-to-Many": {
        "blocking_behavior": "একটি থ্রেড ব্লক করলেও সাধারণত বাকিরা চলতে পারে (একাধিক কার্নেল থ্রেড আছে)",
        "true_parallelism_possible": "হ্যাঁ, তবে উপলব্ধ কার্নেল থ্রেড সংখ্যা পর্যন্ত সীমিত",
        "kernel_overhead": "মাঝামাঝি -- One-to-One-এর চেয়ে কম কার্নেল থ্রেড দরকার",
    },
}

fields = ["blocking_behavior", "true_parallelism_possible", "kernel_overhead"]
labels = {
    "blocking_behavior": "ব্লকিং আচরণ",
    "true_parallelism_possible": "সত্যিকারের প্যারালালিজম",
    "kernel_overhead": "কার্নেল ওভারহেড",
}

for field in fields:
    print(f"--- {labels[field]} ---")
    for model_name, attrs in models.items():
        print(f"  {model_name}: {attrs[field]}")
    print()

    
লক্ষ্য করুন — "সত্যিকারের প্যারালালিজম" সারিতে শুধু Many-to-One-এর উত্তর "না" — বাকি দুটি মডেলই একাধিক কার্নেল থ্রেড ব্যবহার করে বলে একাধিক কোরে সমান্তরালভাবে চলতে পারে। এটিই মূল কারণ Many-to-One আজকের মাল্টি-কোর যুগে ব্যাপকভাবে ব্যবহৃত হয় না।
মূল কথা · Key takeaway

মাল্টিথ্রেডিং মডেল আসলে একটি ম্যাপিং সিদ্ধান্ত — কতগুলো সস্তা ইউজার থ্রেডকে কতগুলো ব্যয়বহুল কিন্তু প্রকৃত কার্নেল থ্রেডে বসানো হবে। এই ম্যাপিং-ই ঠিক করে দেয় ব্লকিং কতটা বিচ্ছিন্ন থাকবে এবং কতটা সত্যিকারের সমান্তরাল কাজ সম্ভব — এবং এই কারণেই আধুনিক OS প্রায় সবসময় One-to-One বেছে নেয়, ওভারহেড সত্ত্বেও।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ Many-to-One মডেলে থ্রেড ব্যবস্থাপনা কেন এত দ্রুত, অথচ এর পারফরম্যান্স ঝুঁকিও এত বড়?

দ্রুত হওয়ার কারণ — থ্রেড তৈরি, সুইচিং সবকিছুই ইউজার-স্পেস লাইব্রেরির ভেতরে ঘটে, কার্নেলে কোনো সিস্টেম কল (L03) লাগে না। কিন্তু ঠিক এই কারণেই কার্নেল আসলে জানেই না একাধিক ইউজার থ্রেড আছে — তার কাছে পুরো প্রসেসটাই একটি একক (একটি কার্নেল থ্রেডের) মতো দেখায়। তাই যখন একটি ইউজার থ্রেড I/O-তে ব্লক করে, কার্নেল পুরো প্রসেসকেই ব্লকড হিসেবে চিহ্নিত করে — অন্য ইউজার থ্রেডরা প্রস্তুত থাকলেও চলতে পারে না।

প্র ০২ একটি সিঙ্গেল-কোর সিস্টেমে One-to-One মডেলের "সত্যিকারের প্যারালালিজম" সুবিধা কি আদৌ কার্যকর?

সিঙ্গেল-কোর সিস্টেমে যেকোনো মুহূর্তে মাত্র একটি কার্নেল থ্রেডই আসলে চলতে পারে (L13-এর মতো একাধিক কোর নেই), তাই "একই সময়ে" সত্যিকারের প্যারালালিজম সেখানে সম্ভব নয়। কিন্তু One-to-One তখনও উপকারী — একটি থ্রেড ব্লক করলে (যেমন I/O-এর জন্য), কার্নেল সহজেই অন্য প্রস্তুত থ্রেডে সুইচ করতে পারে (L01-এর টাইম-শেয়ারিংয়ের মতো), যা Many-to-One মডেলে সম্ভব হতো না।

প্র ০৩ উপরের কোড সেলে "kernel_overhead" সারিতে Many-to-Many-কে "মাঝামাঝি" বলা হয়েছে কেন, "সবচেয়ে কম" নয়?

Many-to-One-এ মাত্র ১টি কার্নেল থ্রেড লাগে (সবচেয়ে কম ওভারহেড), কিন্তু Many-to-Many-তে একাধিক (যদিও ইউজার থ্রেডের চেয়ে কম-বা-সমান সংখ্যক) কার্নেল থ্রেড লাগে — যেমন উপরের ডায়াগ্রামে ৩টি ইউজার থ্রেডের জন্য ২টি কার্নেল থ্রেড। এটি Many-to-One-এর ১টির চেয়ে বেশি, কিন্তু One-to-One-এর ৩টির চেয়ে কম — তাই "মাঝামাঝি" বলা সঠিক, "সবচেয়ে কম" নয়।

অনুশীলন

  1. চিন্তা করুন: একটি ওয়েব সার্ভার একসাথে হাজার হাজার ক্লায়েন্ট রিকোয়েস্ট সামলায় — এখানে Many-to-One মডেল ব্যবহার করলে কী সমস্যা হতে পারে?

    যদি একটি ক্লায়েন্টের রিকোয়েস্ট সামলানো থ্রেড কোনো ডিস্ক I/O বা নেটওয়ার্ক অপেক্ষায় ব্লক করে, Many-to-One মডেলে পুরো সার্ভার প্রসেসই ব্লক হয়ে যাবে — অন্য হাজার হাজার ক্লায়েন্টও অপেক্ষা করতে বাধ্য হবে, যদিও তাদের রিকোয়েস্ট সম্পূর্ণ ভিন্ন ও স্বাধীন। এই কারণেই বাস্তব উচ্চ-পারফরম্যান্স সার্ভারগুলো One-to-One বা Many-to-Many-এর মতো মডেল ব্যবহার করে, যেখানে একটি ব্লকিং রিকোয়েস্ট বাকিদের আটকায় না।

  2. পরীক্ষা করুন: উপরের কোড সেলে models ডিকশনারিতে একটি চতুর্থ এন্ট্রি "Hybrid (বাস্তব উদাহরণ)" যোগ করে দেখুন লুপ স্বয়ংক্রিয়ভাবে চতুর্থ মডেলটিকেও তুলনায় অন্তর্ভুক্ত করে কি না।

    হ্যাঁ — যেহেতু বাইরের লুপটি models.items()-এর ওপর ইটারেট করে এবং fields তালিকার প্রতিটি key প্রতিটি মডেলে খুঁজে বের করে প্রিন্ট করে, একটি চতুর্থ এন্ট্রি (একই তিনটি key blocking_behavior, true_parallelism_possible, kernel_overhead সহ) যোগ করলে সে স্বয়ংক্রিয়ভাবেই প্রতিটি বিভাগে চতুর্থ সারি হিসেবে দেখা যাবে — কোডে কোনো পরিবর্তন ছাড়াই। এটি দেখায় ডেটা ও লজিক আলাদা রাখার (data-driven) সুবিধা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

পূর্ববর্তী পাঠ
থ্রেড বনাম প্রসেস