3.1
Process ও Thread
একটা process-এর নিজের কী থাকে, thread-রা কী ভাগ করে, আর প্রতিটা বানাতে কত খরচ।
55 মিনিট পড়া + Animation
টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।
এই Lesson শেষে তুমি পারবে
- বোঝাতে, Operating System আর তার Kernel কী করে, Program কেন User Mode-এ চলে, আর একটা System Call-এর দাম কত।
- ঠিক করে বলতে, একটা Process-এর নিজের কী কী থাকে (Address Space, PID, File Descriptor, Security Context, Thread), আর Kernel Process Control Block-এ সেগুলোর হিসাব কীভাবে রাখে।
- Thread-রা কী ভাগ করে আর প্রত্যেকে নিজের কাছে কী রাখে তা বলতে, আর Stack কেন আলাদা হয়েও সুরক্ষিত না, তা বোঝাতে।
fork,execআরwaitবর্ণনা করতে, Linuxcloneদিয়ে Thread কীভাবে বানায় তা বলতে, প্রতিটার দাম বলতে, আর Thread-ওয়ালা Program-এforkকেন বিপজ্জনক, তা বোঝাতে।- Pipe থেকে Shared Memory থেকে TCP পর্যন্ত, Inter-Process Communication-এর ঠিক পদ্ধতিটা বাছতে, আর প্রতিটা কীভাবে ভাঙে তা বলতে।
ps-এ Process-এর অবস্থা পড়তে, আর Zombie, Orphan, D অবস্থায় আটকে থাকা Process আর Signal না-শোনা Container ধরতে।SIGTERMদিয়ে Graceful Shutdown লিখতে, আর ১৩৭, ১৩৯, ১৪৩-এর মতো Exit Code পড়তে।- একটা Crash কতদূর ছড়াবে আগে থেকে বলতে, আর PostgreSQL, MySQL, Nginx, Chrome-এর মতো Multi-process ও Multi-thread Server তুলনা করতে।
#এই Lesson কোথায় বসে
Lesson ১.২-এ Server-কে দেখেছি লম্বা সময় ধরে চলা একটা Process হিসেবে, প্রতি Connection-এ Process, প্রতি Connection-এ Thread আর Event Loop, এই Design-গুলো কয়েক লাইনে করে তুলনা করেছি, আর Rolling Deploy ধাপে ধাপে দেখেছি। Lesson ১.১৩-এ দেখেছি Apache-র MPM, Nginx-এর Worker, PHP-FPM আর Gunicorn এই Design-গুলো কীভাবে ব্যবহার করে। দুটো Lesson-এই “Process” আর “Thread” শব্দ দুটো চলেছে, ভেতরটা না খুলেই। এই Lesson-এ আমরা ভেতরটা খুলব।
System Design এই জিনিসগুলোর ওপর যতটা নির্ভর করে, প্রথমে ততটা মনে হয় না। Backend Server, Database, Web Server, Network Programming আর High-concurrency Application, সবই এগুলো দিয়ে তৈরি, আর তাদের বেশিরভাগ সীমা আর ব্যর্থতা আসলে Operating System-এরই সীমা আর ব্যর্থতা। নোটে এই Chapter-এর নয়টা বিষয় আছে, আমরা সেগুলো পড়ব ছয়টা Lesson-এ:
| বিষয় | Lesson |
|---|---|
| Process বনাম Thread, Thread | ৩.১, এই Lesson |
| Concurrency, Context Switching | ৩.২ |
| Thread Pool, Deadlock | ৩.৩ |
| Virtual Memory | ৩.৪ |
| I/O Model, epoll | ৩.৫ |
| Event Loop | ৩.৬ |
আমরা থাকব Operating System-এর দিক থেকে। Scheduling আর Switching হলো Lesson ৩.২-এর বিষয়, আর Code-এ Data Race, Lock আর Memory Model আছে Chapter 14-এ।
#Operating System কী করে
একটা মেশিনে অনেক Program চলে, অথচ Disk আর Network Card একই। কাউকে তো এগুলো ন্যায্যভাবে ভাগ করে দিতে হবে, এক Program যেন আরেকটার ক্ষতি করতে না পারে সেটা দেখতে হবে, আর Hardware-এর ভিন্নতা আড়াল করতে হবে। এই কাজটাই Operating System-এর। এর কেন্দ্রে আছে Kernel: সেই একটা Program, যা মেশিন যতক্ষণ চালু ততক্ষণ Memory-তে থাকে, আর Hardware-এর পুরো নিয়ন্ত্রণ যার হাতে। OS-এর বাকিটা (Shell, Library, ls-এর মতো Tool) সাধারণ Program।
| Kernel-এর কাজ | তোমার জন্য এর মানে | Lesson |
|---|---|---|
| অল্প Core-এ অনেক Program চালানো | Scheduling, Run Queue, Context Switch | ৩.২ |
| প্রতিটা Program-কে নিজের Memory দেওয়া | Virtual Address, Page Table, OOM Killer | ৩.৪ |
| Disk-কে File হিসেবে দেখানো | File System, Page Cache, File Descriptor | ৩.৪, ৩.৫ |
| Network-এর Data সরানো | TCP/IP Stack, Socket, epoll | ১.৯, ৩.৫ |
| Hardware চালানো | Device Driver, Interrupt | |
| কে কী করতে পারবে তা ঠিক করা | User, Permission, Namespace, Cgroup | ৩.১, Chapter 4 |
#User Mode আর Kernel Mode
এই ভাগটা কোনো ভদ্রতার চুক্তি না, CPU নিজে এটা জোর করে মানায়। আজকের প্রতিটা CPU-তে অন্তত দুটো ক্ষমতার স্তর থাকে:
- Kernel Mode (x86-এ Ring 0, ARM-এ
EL1): যেকোনো Instruction, যেকোনো Memory, সব Hardware। - User Mode (x86-এ Ring 3, ARM-এ
EL0): শুধু সাধারণ হিসাব, শুধু Program-এর নিজের Memory-তে।
User Mode-এ কোনো Program Device ছুঁতে, নিজের Memory-র ম্যাপ বদলাতে বা CPU থামাতে গেলে Hardware মানা করে দেয়, নিয়ন্ত্রণ যায় Kernel-এর হাতে, আর সাধারণত Program-টা শেষ। তাই Bug-ওয়ালা Program শুধু নিজেরই ক্ষতি করতে পারে। Chapter 4-এর Hypervisor এই সীমারেখাটাকেই আরও বাড়ায়; আর Container এটা ভাগ করে নেয়: একটা Host-এর সব Container একই Kernel ব্যবহার করে।
#System Call
Program যদি Disk বা Network ছুঁতেই না পারে, তাহলে কাজের কিছু করে কীভাবে? সে চায়। System Call হলো Kernel-এ ঢোকার নিয়ন্ত্রিত পথ: Program একটা Call নম্বর আর Argument Register-এ রেখে বিশেষ একটা Instruction চালায় (x86-64-এ syscall), CPU চলে যায় Kernel Mode-এ আর লাফ দেয় একটা নির্দিষ্ট জায়গায়, Kernel সব যাচাই করে কাজটা করে, তারপর ফেরে User Mode-এ। Linux-এ কয়েকশো System Call আছে: open, read, write, socket, accept, fork, mmap ইত্যাদি। তোমার ভাষার File.read বা fetch শেষমেশ এখানেই পৌঁছায়, সাধারণত C Library হয়ে।
System Call: Kernel-এর ভেতরে ঢোকা
তোমার Program চলে User Mode-এ, Hardware ছোঁয়ার ক্ষমতা তার নেই। বাইরের দুনিয়ার যা কিছু দরকার, Kernel-এর কাছে চাইতে হয়। ধাপে ধাপে দেখো একটা দ্রুত Read, অপেক্ষা করতে হয় এমন একটা Read, আর Kernel যে দুটো অনুরোধ ফিরিয়ে দেয়।
পরিস্থিতি
Data আগে থেকেই Kernel-এর Memory-তে আছে, তাই Disk-এর কোনো কাজ ছাড়াই Call ফিরে আসে।
File-টা File Descriptor ৩ হিসেবে খোলা আছে। Disk-এর সাথে কথা বলার কোনো Instruction Program-এর হাতে নেই, থাকলেও চালানোর অনুমতি পেত না।
দাম কেন গুরুত্বপূর্ণ। একটা Function Call-এ লাগে প্রায় এক ন্যানোসেকেন্ড। সস্তা একটা System Call-এ মোটামুটি ১০০ ns থেকে ১ µs, কারণ Mode বদলানো, যাচাই, আর Spectre ও Meltdown-এর পর যোগ হওয়া নিরাপত্তার ব্যবস্থা। এটা দ্রুতই, কিন্তু সেকেন্ডে লাখ লাখ Call হলে এটাই সময় খেয়ে ফেলে, আর এটা থেকেই Systems Design-এর অনেক কিছু বোঝা যায়:
- Program Buffer করে: ১ KB করে ৬৪ বার না পড়ে একবারে ৬৪ KB, আর Logging Library একসাথে অনেক লাইন লেখে।
clock_gettime-এর মতো প্রচলিত শুধু-পড়ার CallvDSOদিয়ে User Mode-এই হয়ে যায়, Kernel-এ ঢুকতেই হয় না।- High-performance I/O হয় Call একসাথে করে, নয়তো এড়িয়েই যায়:
epoll_waitএকবারে অনেক Event ফেরত দেয়, আর আছেio_uring(Lesson ৩.৫)।
#Process কী
Process হলো একটা Program-এর চলমান রূপ। Program হলো Disk-এ রাখা একটা File, একটা রেসিপি। Process হলো সেই রেসিপি অনুযায়ী রান্না চলছে: Memory ব্যবহার হচ্ছে, File খোলা আছে, Code-এর কোথাও একটা জায়গায় আছে। তুমি Chrome চালু করলে OS তার জন্য এক বা একাধিক Process বানায়; python দুবার চালালে একই Program-এর দুটো Process।
Operating System
|
+---- Chrome process(es)
|
+---- VS Code process
|
+---- MySQL process
|
+---- Python process
প্রতিটা Process-এর নিজের যা থাকে:
| যা আছে | জিনিসটা কী | কেন তোমার জানা দরকার |
|---|---|---|
| Virtual Address Space | Memory-র দিকে তার নিজের দৃষ্টি: Code, Global Data, Heap, Stack | Isolation। একই Address দুই Process-এ আলাদা Memory (Lesson ৩.৪) |
| Process ID (PID) | তার নম্বর, আর তার Parent-এর নম্বর (PPID) | kill, ps, Log, Metric |
| Memory | আসলে ব্যবহার হচ্ছে যে Page-গুলো (Resident Memory) | Capacity Planning, OOM Killer |
| খোলা File Descriptor (FD) | ছোট ছোট নম্বরের একটা টেবিল, যা খোলা File, Socket, Pipe-এর নাম। ০, ১, ২ হলো Standard Input, Output, Error | প্রতিটা Connection একটা করে খায়; Process প্রতি সীমাটা (ulimit -n, Lesson ১.২) আসল দেয়াল |
| Security Context | UID, GID, Capability, আর Container-এ Namespace ও Cgroup | Hack হওয়া Process কতটা ক্ষতি করতে পারে |
| এক বা একাধিক Thread | যে অংশগুলো আসলে CPU-তে চলে | Parallelism, আর পরের পুরো Section |
এছাড়াও থাকে Current Directory, Environment Variable, Signal Handler আর Resource Limit। Linux-এ এ সবই /proc/<pid>/-এ দেখা যায়, “নিজে করে দেখো” অংশে দেখবে।
প্রতিটা Process-এর জন্য Kernel নিজের একটা খাতা রাখে, যাকে পাঠ্যবইয়ে বলে Process Control Block (PCB): অবস্থা, PID, Parent, না চলার সময়ের জন্য Save করা Register, Memory-র ম্যাপ, File-এর টেবিল, হিসাবপত্র। Linux-এ এই Structure-এর নাম task_struct, আর সেটা থাকে প্রতিটা Thread-এর জন্য একটা করে; একই Process-এর Thread-রা Field-গুলো ভাগ করে নেয়।
একটা সাধারণ Address Space, নিচু Address থেকে উঁচুর দিকে:
low +--------------------+
| Code (read-only) | the program's machine code, shareable between processes
| Global data | global and static variables
| Heap ↓ grows| malloc, new, every object
| |
| Shared libraries, |
| memory maps |
| |
| Stack ↑ grows| function calls and local variables
high +--------------------+
| Kernel | reserved for the kernel, no access from user mode
+--------------------+
#Process-দের মাঝের দেয়াল
এক Process আরেক Process-এর Memory পড়তে বা লিখতে পারে না, কারণ সেখানে যাওয়ার মতো কোনো Address তার নেই। প্রতিটা Address যায় সেই Process-এর নিজের Page Table দিয়ে, তাই অন্য Process-এ একই সংখ্যা মানে আলাদা আসল Byte; আর ম্যাপ না থাকলে সাথে সাথে Fault। এই দেয়াল তোমাকে একসাথে তিনটা জিনিস দেয়: Fault Isolation (Crash ভেতরেই থাকে), Security (এক User-এর Process আরেকজনের গোপন তথ্য পড়তে পারে না), আর আলাদা Resource (Limit, Restart, Upgrade)।
দেয়ালে কিছু ইচ্ছাকৃত দরজাও আছে। দুটো Process রাজি হলে Shared Memory ম্যাপ করতে পারে, আর ঠিক অনুমতি থাকলে Debugger ptrace ব্যবহার করতে পারে। নোটে আছে একটা Process অন্যটার Memory-তে “সরাসরি প্রবেশ করতে পারে না”, কথাটা ডিফল্ট হিসেবে ঠিক, তবে শেষ কথা না।
#Thread কী
Thread হলো Process-এর ভেতরে Execution-এর একটা আলাদা ধারা। প্রতিটা Process-এ অন্তত একটা থাকে, অনেকগুলোও থাকতে পারে। CPU-তে চলে শুধু Thread: Lesson ৩.২-এর Scheduler Thread বাছে, Process না।
A program with three threads
|
+---------+---------+
| | |
v v v
Thread 1 Thread 2 Thread 3
UI Network Rendering
এই Chapter-এ মুখস্থ রাখার মতো সবচেয়ে কাজের জিনিস এই ভাগটা:
| Process-এর সব Thread যা ভাগ করে | প্রতিটা Thread-এর নিজের |
|---|---|
| Heap Memory | Stack (Function Call, Local Variable) |
| Global Variable | Program Counter (পরের Instruction) |
| Code Segment | CPU Register |
| খোলা File Descriptor | Thread ID (TID) |
| Signal Handler, Current Directory, UID | Signal Mask, Thread-local Storage, errno |
তার মানে সব Thread একই Object দেখে, একই Socket ব্যবহার করতে পারে, একই Code চালায়; কিন্তু সেই Code-এর কোথায় আছে, আর Local Variable, এগুলো প্রত্যেকের আলাদা। তাই দুটো Thread একই Function একসাথে চালালেও তাদের Local Variable ধাক্কা খায় না।
Process-এর ভেতরে: তার নিজের কী, Thread-রা কী ভাগ করে
Process হলো Resource-এর একটা বাক্স। Thread হলো সেই বাক্সের ভেতরে চলা Execution-এর একটা ধারা। নিরেট বাক্সগুলো Process-এর সব Thread মিলে ভাগ করে; প্রতিটা Dashed কলাম একটা Thread-এর নিজের।
পরিস্থিতি
Program একবার চালু করো, আর Kernel তার জন্য যা যা সাজিয়েছে, একে একে দেখো।
Program চালালেই একটা Process তৈরি হয়: তার একটা চলমান রূপ। একই Program দুবার চালালে দুটো Process, দুটো PID। Disk-এ থাকা Program File শুধু রেসিপি।
তিনটা জিনিসে মানুষ ধরা খায়:
১. সব Memory কার্যত ভাগের না, আর সবটা নিরাপদও না। Stack-এর Local Variable কার্যত ব্যক্তিগত, আর Register সত্যিই ব্যক্তিগত। কিন্তু সব Stack একই Address Space-এ থাকে, তাই এক Thread-এর ভুল Pointer আরেকজনের Stack মুছে লিখতে পারে। আলাদা, হ্যাঁ; সুরক্ষিত, না।
২. File Descriptor ভাগের। এক Thread একটা Socket বন্ধ করলে সবার জন্যই বন্ধ। দুবার Close করলে সেটা গিয়ে লাগতে পারে এমন Descriptor-এ, যা আরেক Thread এইমাত্র একই নম্বরে পেয়েছে; এই Bug খুঁজে পাওয়া ভয়াবহ কঠিন।
৩. ভাগাভাগিতে কথা বলা সহজ, ঠিক রাখা কঠিন। Thread-রা শুধু Memory-তে লিখেই কথা বলে, System Call লাগে না। তবে সমন্বয় ছাড়া দুজন একই Data-য় লিখলে হয় Race; এখানে শুধু নামটা জানলে, শিখবে Chapter 14-এ।
#Kernel Thread আর User-level Thread
এই Lesson-এর Thread হলো Kernel Thread: Kernel প্রত্যেকটাকে চেনে আর Schedule করে, একের বদলে এক। Linux-এ pthread_create আর Java-র Platform Thread এটাই দেয়। Goroutine, Java Virtual Thread আর অন্য Green Thread আরও হালকা: ভাষার Runtime অনেকগুলোকে অল্প কয়েকটা Kernel Thread-এ চালায়। এগুলো আছে Lesson ৩.৬-এ, কারণ কাজ করতে এদের Event Loop-এর যন্ত্রপাতি লাগে।
#Process বনাম Thread
| বিষয় | Process | Thread |
|---|---|---|
| অর্থ | চলমান Program | তার ভেতরে Execution-এর ধারা |
| Memory | সাধারণত আলাদা Address Space | Process-এর Memory ভাগ করে, Stack নিজের |
| Resource | তুলনামূলক বেশি | তুলনামূলক কম |
| তৈরি করা | তুলনামূলক দামি | তুলনামূলক হালকা |
| যোগাযোগ | IPC লাগে | সরাসরি, Shared Memory দিয়ে |
| Failure Isolation | শক্ত | দুর্বল: একই Process-এর Thread একে অন্যকে প্রভাবিত করতে পারে |
| Context Switch | সাধারণত বেশি দামি (Address Space বদলায়) | একই Process-এর ভেতরে সাধারণত কম দামি (Lesson ৩.২) |
| Security-র সীমানা | হ্যাঁ | না |
| মেশিন পেরিয়ে ছড়ানো | হ্যাঁ, Network IPC দিয়ে | না |
| যে সীমা দেখতে হবে | Process প্রতি Memory, Shared Store-এ Connection | Stack Memory, pids.max, Lock Contention |
#Process আর Thread তৈরি
#fork, exec আর wait
Unix “একটা Program চালাও” কাজটাকে দুই ধাপে ভাগ করে, আর এই ভাগটাই অসাধারণ শক্তিশালী।
fork: আমার কপি বানাও
fork()এমন একটা Child বানায়, যা Parent-এর প্রায় হুবহু কপি। এটা দুবার ফেরে: Child-এ ০, আর Parent-এ Child-এর PID। এই সময়ে Memory আসলে কপি হয় না: Parent আর Child Copy-on-Write দিয়ে Page ভাগ করে, আর কেউ লিখলে তবেই সেই Page কপি হয়। কৌশলটা আছে Lesson ৩.৪-এ।exec: ওই Program হয়ে যাও
execve()বর্তমান Memory-র জায়গায় নতুন Program বসায়। PID, Parent আর খোলা File Descriptor থেকে যায়, শুধু close-on-exec দাগানোগুলো বাদে।forkআরexec-এর মাঝে Child সব গুছিয়ে নিতে পারে: Shell এভাবেই>Redirection আর Pipe বানায়, যে Program চালাচ্ছে তার কোনো বিশেষ সাহায্য ছাড়াই। সফল হলেexecআর ফেরে না; ফিরলে বুঝবে কিছু একটা ব্যর্থ হয়েছে, আর Child-কে বের হয়ে যেতে হবে।wait: ফলাফল নাও
wait()বাwaitpid()Parent-কে Child-এর Exit Status দেয়, আর Kernel তার খাতা মুছে ফেলতে পারে। যে Parent এটা কখনো করে না, সে Zombie বানায়; সেই গল্পে একটু পরেই আসছি।
Windows-এ fork নেই; তার CreateProcess সরাসরি নতুন Program চালায়। Windows-এ Process বানানো অনেক ধীর হওয়ার এটা একটা কারণ, আর Windows Server-গুলো এজন্যই Thread-এর দিকে ঝোঁকে। Unix-এ posix_spawn একই রকম এক ধাপের পথ দেয়।
#Linux-এ Thread
Linux-এর Kernel-এ Thread-এর আলাদা কোনো ধারণা নেই। দুটোই Task, বানানো হয় clone() দিয়ে। Flag ঠিক করে কী ভাগ হবে: Memory, File টেবিল আর Signal Handler ভাগ করলে পাও একটা Thread, যার TID নতুন, কিন্তু PID (তার Thread Group) একই। এতে অনেক খুঁটিনাটি পরিষ্কার হয়: Thread-এর Stack দেখা যায় Process-এর Memory Map হিসেবে, আর pids.max-এর মতো সীমায় Process-এর সাথে Thread-ও গোনা হয়।
#কোনটার দাম কত
এগুলো একটা সাধারণ Linux Server-এর আনুমানিক মাপ। ভরসা করার আগে নিজের System-এ মেপে নাও।
| কাজ | মোটামুটি দাম | কিসের ওপর নির্ভর করে |
|---|---|---|
| Function Call | প্রায় ১ ns | |
| System Call | ১০০ ns থেকে ১ µs | CPU, নিরাপত্তার ব্যবস্থা |
| Thread বানানো | কয়েক দশ মাইক্রোসেকেন্ড | Stack সাজানো |
ছোট Process-এ fork | কয়েক দশ থেকে কয়েকশো মাইক্রোসেকেন্ড | Page Table কপি |
কয়েক দশ GB ব্যবহার করা Process-এ fork | কয়েক মিলিসেকেন্ড থেকে আরও অনেক | Memory-র সাথে Page Table বাড়ে |
fork + exec + Runtime লোড (Python, Java) | কয়েক দশ মিলিসেকেন্ড থেকে কয়েক সেকেন্ড | Program চালু হওয়া, Kernel না |
| Thread-এর Memory | Linux glibc-তে ডিফল্ট ৮ MB Address Space, ৬৪-bit JVM-এ প্রায় ১ MB; RAM লাগে শুধু ছোঁয়া Page-এর, প্রায়ই কয়েক দশ KB | ulimit -s, -Xss |
| Process-এর Memory | কয়েক MB, কোনো Runtime-এ কয়েকশো MB-ও; ভাগের Code আর Copy-on-Write Page একবারই গোনা হয় | ভাষা |
এ থেকে Design-এর তিনটা সিদ্ধান্ত আসে:
- প্রতি Request-এ বানিও না। প্রতি Request-এ একটা Thread বা Process চালু করলে এই দাম বারবার দিতে হয়। Pool দিয়ে আবার ব্যবহার করো (Lesson ৩.৩), বা আগেই Worker Fork করে রাখো (Lesson ১.১৩)।
- Process যত বড়,
forkতত ধীর। Redis Snapshot লিখতে একটা Child Fork করে (BGSAVE), আর বড় Instance-এ শুধু এই Fork-এর জন্যই কয়েক দশ মিলিসেকেন্ড বা তারও বেশি Command চলা থেমে থাকতে পারে। Snapshot চলার সময় Write হলে Page কপি হয়, তাই Redis Server-এর Data-র চেয়ে অনেক বেশি Memory লাগতে পারে। - দশ হাজারের ঘরে না যাওয়া পর্যন্ত Thread সস্তা। এক লাখ অলস WebSocket Connection (Lesson ২.৬) মানে এক লাখ Stack আর বিপুল Scheduling। Lesson ৩.৫ আর ৩.৬ এই সমস্যারই সমাধান।
কাজ তৈরি: fork, exec, wait, আর একটা Thread
Unix-এ নতুন Process শুরু হয় চলমান একটার কপি হিসেবে, আর আলাদা একটা Call তাতে নতুন Program বসায়। Thread একই Kernel যন্ত্র ব্যবহার করে, কিন্তু কপি না করে সব ভাগ করে নেয়।
পরিস্থিতি
প্রতিবার Command লিখলে Shell যা করে, এখানে ls।
Shell নিজের একটা কপি চায়। খালি Process বানানোর উপায় নেই: Unix-এ Process সবসময় অন্য Process থেকে জন্মায়, পেছনে যেতে যেতে একদম PID 1 পর্যন্ত।
#Process-এর অবস্থা
একটা Process বা Thread সবসময় কোনো একটা অবস্থায় থাকে, আর Server-এ সেটা খুব কমই “চলছে”।
| অবস্থা | মানে | ps stat |
|---|---|---|
| New | তৈরি হচ্ছে | |
| Ready | চলতে পারে, Core-এর অপেক্ষায় | R |
| Running | এখন Core-এ | R |
| Waiting (Interruptible) | কোনো Event-এর জন্য ঘুমাচ্ছে: Socket-এর Data, Timer, Lock | S |
| Waiting (Uninterruptible) | Kernel-এর Disk-এর পথে ঘুমাচ্ছে; Signal-কে অপেক্ষা করতে হবে | D |
| Stopped | SIGSTOP বা Debugger থামিয়েছে | T |
| Zombie | বের হয়েছে, Status নেওয়া হয়নি | Z |
পড়ার কিছু কৌশল:
- সুস্থ অলস Server-এ প্রায় সবই
S। Database-এর অপেক্ষায় থাকা Thread-এ CPU খরচ নেই, তাই “Thread-এর সংখ্যা” আর “CPU ব্যবহার” সম্পর্কহীন দেখাতে পারে। - অল্প Core-এ অনেক
Rমানে CPU ভরে গেছে; Load Average আর Latency-তে Queue দেখবে। - অনেক
Dমানে Storage-এ গোলমাল: চেনার চিহ্ন হলো Load Average বেশি, অথচ CPU কম (Lesson ১.২-এ দেখেছ, Linux Load-এDগোনে)। এমনকিSIGKILL-ও Thread না জাগা পর্যন্ত অপেক্ষা করে।
Process-এর অবস্থা: জীবনটা কোথায় কাটে
বেশিরভাগ সময় Server-এর Thread চলে না। Kernel প্রত্যেককে এই অবস্থাগুলোর মধ্যে সরায়, আর ps-এর stat কলাম বলে দেয় কে এখন কোথায়।
পরিস্থিতি
fork থেকে খাতা মুছে যাওয়া পর্যন্ত।
fork এইমাত্র Task বানাল। Kernel তার খাতা আর Memory-র ম্যাপ সাজাচ্ছে। এখনো চলতে পারে না।
#Zombie আর Orphan
দুটো নাম শুনতে কাছাকাছি, আর Interview-তে প্রায়ই গুলিয়ে যায়।
Zombie হলো এমন Process, যে বের হয়ে গেছে, কিন্তু Parent এখনো wait ডাকেনি। তার Memory আর File আর নেই। আছে শুধু Exit Status-সহ Kernel-এর একটা খাতা, আর তার PID। প্রতিটা Process-ই এক মুহূর্তের জন্য Zombie হয়, এটা স্বাভাবিক। Bug হয় তখন, যখন Parent কখনো Reap করে না: ps ভরে যায় <defunct>-এ, আর একসময় fork ব্যর্থ হয়, কারণ PID-এর জায়গা (/proc/sys/kernel/pid_max) বা Container-এর pids.max শেষ।
- Zombie-কে মারা যায় না: সে তো আগেই মৃত।
kill -9কিছুই করে না। - ঠিক করতে হয় Parent-কে:
SIGCHLDসামলাও আর লুপেwaitpidডাকো, বা Status না লাগলেSIGCHLDIgnore করে দাও। জরুরি অবস্থায় Parent-কে মেরে দাও, তখন init দত্তক নিয়ে Zombie-গুলোকে Reap করে।
Orphan হলো এমন জীবিত Process, যার Parent মারা গেছে। তাকে মারা হয় না। Kernel তাকে PID 1-এর (বা কাছের কোনো Subreaper-এর) কাছে দেয়, আর সে বের হলে এই নতুন Parent তাকে Reap করে। Orphan কখনো ইচ্ছাকৃত (nohup, Daemon), কখনো চমক: Master-এর পরেও বেঁচে থাকা Worker উত্তরাধিকারে পাওয়া Listening Socket ধরে রাখে, তাই Restart করা Service ব্যর্থ হয় address already in use দিয়ে।
Container-এ এটা তোমার ঘাড়ে পড়ে। তোমার Application তার PID Namespace-এ PID 1, তাই ওই Container-এর Orphan-রা যায় তোমার App-এর কাছে, আর সে জানেই না যে তাদের Reap করতে হবে। ছোট্ট একটা init ব্যবহার করো: docker run --init, বা Entrypoint হিসেবে tini।
Zombie আর Orphan
Zombie হলো এমন Child, যে মারা গেছে অথচ Parent তার Exit Status পড়েনি। Orphan হলো এমন জীবিত Child, যার Parent মারা গেছে। সমস্যা আলাদা, সমাধানও আলাদা।
পরিস্থিতি
সুস্থ অবস্থা: মুহূর্তের জন্য Zombie।
Exit করলে প্রতিটা Process-ই Zombie হয়। এটা স্বাভাবিক, আর অল্প সময়ের: Parent চাইতে পারে ভেবে Kernel Exit Status রেখে দেয়।
#Inter-Process Communication
দেয়ালে Process-দের একটা দরজা লাগে। IPC হলো সেই দরজাগুলোর পরিবার।
| পদ্ধতি | কীভাবে কাজ করে | কপি | নাগাল | কোথায় ব্যবহার | কীভাবে ভাঙে |
|---|---|---|---|---|---|
| Pipe | Kernel Buffer দিয়ে একমুখী Byte-এর ধারা (Linux-এ ডিফল্ট ৬৪ KB) | ২ | সম্পর্কিত Process | Shell Pipeline, Parent-Child কথাবার্তা | ভরে গেলে লেখক আটকে যায়; পাঠক চলে গেলে SIGPIPE |
| Named Pipe (FIFO) | File-এর নামওয়ালা Pipe | ২ | একই মেশিন | সহজ, সম্পর্কহীন Process | একই, আর অপর পক্ষ না থাকলে খুলতেই আটকে যায় |
| Unix Domain Socket | Socket API, Address হিসেবে File Path | ২ | একই মেশিন | Nginx → PHP-FPM, লোকাল Database Connection, Docker API, Sidecar | পুরনো Socket File, ভুল File Permission |
| TCP / UDP Socket | পুরো Network Stack | ২ বা বেশি | যেকোনো জায়গায় | Service, Database, মেশিনের মাঝের সবকিছু | Timeout, আংশিক ব্যর্থতা |
| Shared Memory | একই Page দুই পক্ষে ম্যাপ করা | ০ | একই মেশিন | PostgreSQL-এর Shared Buffer, বিপুল হারের Data | Race; Crash সবার জন্য এটা নষ্ট করতে পারে |
| Message Queue (POSIX, System V) | Kernel-এ রাখা Message-এর Queue | ২ | একই মেশিন | আজকাল কম; App সাধারণত Socket নেয় | ভরে যায়, আকারের সীমা |
| Signal | Asynchronous-ভাবে পৌঁছানো একটা নম্বর | নেই | একই মেশিন | খবর দেওয়া: থামো, Reload, Child বের হয়েছে | Data বয় না, মিলে যেতে পারে, Handler-এর Code সীমিত |
| File, Memory-mapped File | File System হয়ে | ভিন্ন ভিন্ন | একই মেশিন, বা Shared Storage | Batch হাতবদল, সহজ Lock | আধা পড়া, Locking |
কীভাবে বাছবে। একই মেশিন আর Request-এর ধারা: Unix Domain Socket, কারণ Socket API পরে সহজে TCP-তে নেওয়া যায়। একই মেশিন, বিপুল হার, আর নিজের Lock-এ ভরসা আছে: Shared Memory। কখনো অন্য মেশিনে যেতে পারে এমন যেকোনো কিছু: ঠিকঠাক Protocol-সহ TCP (HTTP, gRPC), বা Message Broker (Chapter ৩০–৩২)। বাস্তবে একটা System-এর Microservice মানে Network দিয়ে IPC করা Process, আর বাড়তি সব ব্যর্থতা আসে ওই “Network” শব্দটা থেকেই।
লোকাল IPC ভাঙেও সহজভাবে। Pipe হয় কাজ করে, নয় জানায় ভেঙেছে; Network Call-এ উত্তরই না আসতে পারে, আর কাজটা হলো কি না, তুমি বুঝতেই পারো না।
দেয়াল পেরিয়ে কথা: Inter-Process Communication
Process একে অন্যের Memory পড়তে পারে না, তাই প্রতিটা পদ্ধতিতেই Kernel হয় Data বয়ে নেয়, নয়তো একটা ভাগের জায়গা সাজিয়ে দেয়। দেখো কোনটার খরচ কত, আর কোনটা কীভাবে ভাঙে।
পরিস্থিতি
ls | wc -l: সম্পর্কিত Process-দের মধ্যে একমুখী Byte।
Shell pipe ডাকে, আর Kernel দুটো File Descriptor সহ একটা Buffer বানায়: পড়ার মাথা আর লেখার মাথা। fork-এর পর দুই পক্ষ একেকটা পায়, তাই Program-গুলো সাধারণ Standard Output আর Input-ই ব্যবহার করে।
#Signal আর Graceful Shutdown
Signal হলো Kernel থেকে আসা ছোট্ট Asynchronous খবর, চেনা যায় শুধু নম্বর দিয়ে। আসতে পারে যেকোনো Instruction-এর মাঝে। তখন Process তার Handler চালায়, আর Handler না থাকলে ডিফল্ট কাজটা হয়।
| Signal | নম্বর | ডিফল্ট | মানে আর সাধারণ ব্যবহার |
|---|---|---|---|
SIGHUP | ১ | শেষ | Terminal বন্ধ; রীতি অনুযায়ী Daemon-এর জন্য “Config আবার পড়ো” |
SIGINT | ২ | শেষ | Ctrl+C |
SIGKILL | ৯ | শেষ | Kernel এখনই শেষ করে দেয়। ধরা, আটকানো বা উপেক্ষা করা যায় না |
SIGSEGV | ১১ | শেষ + Core Dump | অবৈধ Memory ছোঁয়া |
SIGPIPE | ১৩ | শেষ | বন্ধ Pipe বা Socket-এ লেখা; Server এটা উপেক্ষা করে Write Error সামলায় |
SIGTERM | ১৫ | শেষ | “দয়া করে থামো।” kill, systemctl stop, docker stop আর Kubernetes প্রথমে এটাই পাঠায় |
SIGCHLD | x86 Linux-এ ১৭ | উপেক্ষা | একটা Child বের হয়েছে |
SIGSTOP / SIGCONT | x86 Linux-এ ১৯ / ১৮ | থামা / চলা | Pause আর Resume। SIGSTOP ধরা যায় না |
Exit Code বলে দেয় কী ঘটেছে। Signal-এ Process মরলে Shell আর Container Platform জানায় ১২৮ + Signal-এর নম্বর:
| Exit Code | মানে | কোথায় দেখবে |
|---|---|---|
| ০ | সফল | |
| ১, ২, … | Program-এর নিজের Error | Application Log |
| ১২৬ / ১২৭ | চালানো যাচ্ছে না / Command নেই | Image, Path, CMD |
| ১৩৭ (১২৮ + ৯) | SIGKILL: OOM Killer, বা Grace Period শেষ | dmesg, kubectl describe pod-এ কারণ |
| ১৩৯ (১২৮ + ১১) | SIGSEGV: Memory Bug, সাধারণত Native Code | Core Dump, সাম্প্রতিক Native Library বদল |
| ১৪৩ (১২৮ + ১৫) | Handler ছাড়া SIGTERM | Handler যোগ করো; কাটা পড়া Request দেখো |
#Graceful Shutdown লেখা
Lesson ১.২-এ Deploy-এর ধাপ দেখেছ। এবার Process-এর দিকটা, Node.js-এ:
const http = require('node:http');
const server = http.createServer((req, res) => res.end('ok\n'));
server.listen(8080);
process.on('SIGTERM', () => {
console.log('SIGTERM: draining');
// Stop accepting; the callback fires when in-flight requests are done.
server.close(() => {
console.log('drained, exiting');
process.exit(0);
});
// Give up shortly before the orchestrator's SIGKILL would.
setTimeout(() => process.exit(1), 25_000).unref();
});
যেকোনো ভাষায় ক্রমটা একই:
১. Readiness ব্যর্থ করো, যাতে Traffic থামে; তারপর একটু অপেক্ষা করো, কারণ Load Balancer সাথে সাথে বোঝে না। Kubernetes-এ Endpoint সরানো আর SIGTERM একই সময়ে হয়, পরপর না, তাই preStop Hook-এ কয়েক সেকেন্ড Sleep দেওয়া প্রচলিত।
২. নতুন কাজ নেওয়া বন্ধ করো: Listening Socket বন্ধ, Queue থেকে নেওয়া থামাও।
৩. চলমান কাজ শেষ করো, এমন সময়সীমায়, যা Grace Period-এর চেয়ে ছোট (Kubernetes-এ ডিফল্ট ৩০ সেকেন্ড, docker stop-এ ১০)।
৪. Resource ছেড়ে দাও: Log আর Metric Flush করো, অসমাপ্ত Message Queue-তে ফেরত দাও, Database Connection বন্ধ করো।
৫. Exit 0।
যে ভাষায় Native Code-এ Handler লেখো, সেখানে Signal Handler শুধু Async-signal-safe Function-এর ছোট্ট একটা তালিকাই ডাকতে পারে, কারণ সে যেকোনো কিছুকে থামিয়ে চলে আসতে পারে, এমনকি malloc-কেও। তাই Handler শুধু একটা Flag বসায় বা Pipe-এ লেখে, বাকিটা করে সাধারণ Code। Process-এ পাঠানো Signal যায় এমন যেকোনো একটা Thread-এ, যে সেটা Block করেনি; তাই Multithreaded Server সাধারণত সব জায়গায় Signal Block করে, আর একটা Thread রাখে sigwait-এর জন্য।
এগুলোর কিছুই না চললে কী হবে, সেভাবে Design করো। বিদ্যুৎ চলে যাওয়া, Kernel Panic বা SIGKILL কোনো সতর্কতা দেয় না। Graceful Shutdown Deploy-কে পরিচ্ছন্ন করে, কিন্তু Crash-কে নিরাপদ করে শুধু Idempotent Operation, Transaction আর Durable Log।
Signal আর Graceful Shutdown
প্রতিটা Deploy শেষ হয় পুরনো Process-এর SIGTERM পাওয়া দিয়ে। ব্যবহারকারী Error দেখবে কি না, সেটা নির্ভর করে Process এরপর কী করে তার ওপর। এখানে Grace Period হলো Kubernetes-এর ডিফল্ট ৩০ সেকেন্ড।
পরিস্থিতি
Handler নতুন কাজ বন্ধ করে, পুরনো কাজ শেষ করে, আর বের হয়।
প্রথমে Traffic সরানো হয়। Kubernetes-এ এটা Signal-এর আগে না, একই সময়ে হয়; তাই বন্ধ হতে থাকা Server-এ Request যাওয়া ঠেকাতে preStop Hook-এ অল্প Sleep দেওয়া একটা প্রচলিত উপায়।
#Crash Isolation: কার সাথে কে মরে
নিয়মটা সহজ: একটা Bug-এর ক্ষতির সীমা হলো তার সাথে Memory ভাগ করে যে Unit।
এক Thread ভুল Pointer ধরলে Kernel পাঠায় SIGSEGV, আর ডিফল্টে পুরো Process মরে: সব Thread, চলমান সব Request। এটা না হয়ে উপায় নেই। Bug-টা ভাগের Memory-র যা খুশি নষ্ট করে থাকতে পারে, তাই বাকি Thread-দের আর বিশ্বাস করা যায় না। আলাদা Process হলে ব্যাপারটা আলাদা: একটা Crash করলে বাকিদের কিছু হয় না, যদি না তারা Memory ভাগ করে।
এখানে তিনটা জিনিস প্রায়ই গুলিয়ে যায়:
| ঘটনা | কী মরে |
|---|---|
| Native Code-এ Segfault (যেকোনো Thread-এ) | পুরো Process |
| Java বা Python-এ, Main ছাড়া অন্য Thread-এ না-ধরা Exception | শুধু সেই Thread; Process চলে, হয়তো আধা-বদলানো Object বা ধরে রাখা Lock নিয়ে |
| Node.js-এ না-ধরা Exception, বা Go-তে যেকোনো Goroutine-এ Recover না করা Panic | পুরো Process |
| C++-এ না-ধরা Exception | পুরো Process, std::terminate হয়ে |
| Memory শেষ (Linux OOM Killer) | Kernel যে Process বাছে সে পুরোটা, তারপর হয়তো আরও |
| গভীর Recursion-এ Stack Overflow | Native Code-এ সাধারণত পুরো Process; Java আর Python-এ ধরা যায় এমন Error |
Security-র চেহারাও একই। ২০১৪ সালের OpenSSL-এর Heartbleed Bug-এ Attacker Server-এর Process Memory পড়তে পেরেছিল, Private Key-সহ, কারণ সবকিছু একই Address Space-এ ছিল। SSH-এর Privilege Separation-এর মতো, স্পর্শকাতর কাজ নিজের Process-এ সরিয়ে নিলে Bug-এর নাগাল কমে।
#Multi-process বনাম Multi-thread Server
Lesson ১.২ Model-গুলোর সাথে পরিচয় করিয়েছে। এবার বোঝা যাবে আসল System-গুলো কেন যা বেছেছে তা-ই বেছেছে।
| System | Model | কেন, আর দাম কী |
|---|---|---|
| PostgreSQL | প্রতি Connection-এ একটা OS Process, Postmaster থেকে Fork করা, Buffer-এর জন্য Shared Memory | ১৯৮০-র দশকের Design, খুব Portable। প্রতিটা Backend কয়েক MB খায়, তাই max_connections সাধারণত কয়েকশোর মধ্যে রাখা হয় (ডিফল্ট ১০০)। তাই PgBouncer। একটা Backend Crash করলে Postmaster বাকিদেরও Reset করে, কারণ Shared Memory নষ্ট হয়ে থাকতে পারে |
| MySQL | একটা mysqld Process, প্রতি Connection-এ একটা Thread | প্রতি Connection-এ সস্তা, হাজারো Connection বাস্তবসম্মত। Crash করলে পুরো Server যায়, Restart-এর পর InnoDB Redo Log দিয়ে Recovery করে |
| Nginx | Master আর প্রতি Core-এ একটা Worker Process, প্রত্যেকে একটা Event Loop | Worker-রা আলাদা; মরা Worker কয়েক মিলিসেকেন্ডে বদলে যায়। State ভাগ করতে স্পষ্ট Shared Memory Zone লাগে (Lesson ১.১৩) |
| Apache | prefork (Process), worker আর event (Process-এর ভেতরে Thread) | একটা Product-এর ভেতরেই Trade-off: Thread-safe না এমন Library-র জন্য prefork, Scale-এর জন্য event |
| PHP-FPM, Gunicorn | আগে থেকে Fork করা Worker-এর Pool | খারাপ Request আর Memory Leak আটকায়, CPU-র কাজে Python-এর GIL এড়ায় (Lesson ৩.২)। N-টা Request পর Worker বদলানো যায় |
| Tomcat, পুরনো ধাঁচের Java ও .NET | একটা Process, একটা Thread Pool | Cache আর Connection Pool Memory-তে ভাগ হয়, দ্রুত চালু হয়। একটা খারাপ Native Library বা Heap শেষ হওয়া সব ফেলে দেয় |
| Redis | একটা Process; Command একটা Thread-এ, ইচ্ছে হলে I/O Thread; Persistence-এর জন্য Fork | Data-র পথে Lock নেই; Persistence চলে Copy-on-Write-এ। Lesson ৩.৬ দেখো |
| Chrome | Browser, GPU, Network আর অনেক Renderer Process, ভেতরে Thread | খারাপ Page অন্য Site-কে Crash করাতে বা পড়তে পারে না (Site Isolation)। দাম হলো Memory |
কার সাথে কে মরে: Thread, Process আর আসল Server
একটা Bug-এর ক্ষতির সীমা হলো তার সাথে Memory ভাগ করে যে Unit। দেখো একটা Thread-এর Segfault, সাধারণ Exception, Worker Process, Chrome-এর Tab, PostgreSQL আর MySQL।
পরিস্থিতি
একটা Thread-এ একটা ভুল Pointer, একই Process-এ ২০০টা Request।
তিনটা Thread দেখানো হলো। সবাই একই Heap ব্যবহার করে, আর এটাই তাদের দ্রুত ও সস্তা করে।
#কীভাবে বাছবে
| তোমার যা দরকার | যেদিকে ঝুঁকবে | কারণ |
|---|---|---|
| যে Code-এ পুরো ভরসা নেই (Plugin, Native Library, User Content) | Process | দেয়াল Crash আর Exploit আটকায় |
| বড় ভাগের In-memory State (Cache, Connection Pool) | Thread | একটাই Heap, কপি নেই |
| Global Lock-ওয়ালা ভাষায় সব Core-এ CPU-র কাজ | Process | Thread ওই ভাষার Code Parallel চালাতে পারে না (CPython-এর ডিফল্ট Build) |
| বেশিরভাগ সময় অলস অনেক Connection | Event Loop বা Virtual Thread | Lesson ৩.৫ আর ৩.৬ দেখো |
| User বা ক্ষমতার স্তরের মধ্যে Security-র ভাগ | Process (বা Container, বা VM) | Thread Credential আর Memory ভাগ করে |
| দ্রুত চালু, প্রতি Unit-এ কম Memory | Thread | সাজানোর কাজ অনেক কম |
| একটা অংশ দ্রুত Restart, বা Leak আটকানো | Process | Worker বদলানো রোজকার কাজ |
| এক মেশিনের বাইরে Scale | Network-এ কথা বলা Process | Thread মেশিনের সীমানা পার হতে পারে না |
বাস্তবে আধুনিক System এগুলো মিলিয়ে ব্যবহার করে: মেশিনে কয়েকটা Process বা Container প্রতি একটা, প্রত্যেকের ভেতরে Thread-এর Pool বা Event Loop, ছড়ানো অনেক মেশিনে, আর State থাকে বাইরে। Process মরলে Container Platform Restart করে, তাই পুরো Fleet-এর ভরসা এই Process-স্তরের Isolation-এই। Bulkhead Pattern আলাদা Dependency-কে আলাদা Pool দিয়ে একই ধারণা বাড়ায় (Lesson ৩.৩)।
#Production-এর পরিচিত সমস্যা
| লক্ষণ | সম্ভাব্য কারণ | কী দেখবে | সমাধান |
|---|---|---|---|
| “fork: Resource temporarily unavailable” | pids.max, ulimit -u বা pid_max-এ ঠেকেছে, প্রায়ই Thread Leak-এ | ps -eLf | wc -l, cat /sys/fs/cgroup/pids.current | সীমিত Pool, Leak সারাও |
Java: OutOfMemoryError: unable to create native thread | একই Task-এর সীমা, বা Address Space নেই, অথচ Heap ঠিক দেখায় | Thread-এর সংখ্যা, pids.max | Pool, ছোট -Xss |
| “too many open files” | FD-এর সীমা, বা Connection বা File Leak | ls /proc/<pid>/fd | wc -l | LimitNOFILE বাড়াও, যা খোলো তা বন্ধ করো |
<defunct> বাড়ছে | Parent Reap করছে না | ps -eo pid,ppid,stat,comm | grep Z | Parent ঠিক করো; Container-এ init দাও |
| থামতে ঠিক ১০ বা ৩০ সেকেন্ড, তারপর Exit ১৩৭ | Signal App-এ পৌঁছায়নি, বা Handler নেই | CMD কি Shell Form? | Exec Form, --init, Handler |
| প্রতি Deploy-এ Error | Graceful Shutdown নেই, বা SIGTERM-এর পরেও Traffic আসছে | Exit Code ১৪৩, 502-এর ঝাপটা | Handler, preStop, Drain |
| এলোমেলো Exit ১৩৯ | Native Memory Bug | Core Dump, dmesg | Library Update বা আলাদা করো |
| Load Average বেশি, CPU অলস | Thread D-তে: Storage ঝুলে আছে | ps -eo stat,comm | grep ^D | Storage বা Mount ঠিক করো |
Service Restart হয় না: address already in use | কোনো Orphan Socket পেয়ে বসে আছে | ss -tlnp | তাকে মারো, close-on-exec দাও, systemd-র Cgroup Kill ব্যবহার করো |
SIGTERM Handler মাঝে মাঝে আটকে যায় | Handler অনিরাপদ কাজ করে Lock-এ Deadlock হয় | Stack Dump | Handler শুধু Flag বসাবে |
#System Design-এ Process আর Thread
- Process তোমার ব্যর্থতা আর Deploy-এর Unit; মেশিন Hardware ব্যর্থতার Unit। ধরে নাও যেকোনো Process যেকোনো মুহূর্তে মরতে পারে, State বাইরে রাখো (Lesson ১.৩), আর কয়েকটা চালাও।
- Thread আর Process আসল Capacity খায়। ১ MB Stack-এর হাজারটা Thread, প্রত্যেকে ১০০ KB ছুঁলে প্রায় ১০০ MB Memory, সাথে Scheduling-এর খরচ। ৫,০০০ Process-ওয়ালা Database মানে Outage অপেক্ষা করছে। সামনে একটা Pool দাও।
- Connection-এর সীমা আসলে Process আর FD-র সীমা। Database-এর Connection Cap এই দাম থেকেই আসে। Capacity-র হিসাবে গোনো: প্রতি App Instance-এ Connection × Instance-এর সংখ্যা।
- Stateless Process সহজে Restart হয়। Memory-র গরম Cache Process-এর সাথেই যায়, তাই Restart-এ Cold Start-এর ঝাপটা আসতে পারে; ধীরে ধীরে Roll করো।
- Isolation একটা নব: Thread → Process → Container → Virtual Machine → আলাদা মেশিন। প্রতি ধাপে দাম বাড়ে, আটকানোর ক্ষমতাও বাড়ে (একদম শেষ প্রান্তটা Chapter 4-এ)।
- Interview-এর দিক: “Process বনাম Thread?” উত্তর দাও কী ভাগ হয়, ক্ষতির সীমা কত, আর দাম কত দিয়ে। “PostgreSQL-এর PgBouncer লাগে কেন, আর MySQL-এর সাধারণত লাগে না কেন?” “Zombie কী, মারা যায়?” “Container থামতে ৩০ সেকেন্ড লাগে কেন?” “Exit ১৩৭ মানে কী?” “Plugin Thread-এ চালাবে, না Process-এ?” “একটা ভুল Pointer কীভাবে ২০০টা Request ফেলে দেয়?”
#নিজে করে দেখো
Linux মেশিনে, বা procps আর python3 আছে এমন Container-এ:
# Look at processes: pid, parent, state, thread count, resident KB, name
ps -eo pid,ppid,stat,nlwp,rss,comm --sort=-nlwp | head
# Every thread of every process (LWP is the TID)
ps -eLf | head
# Everything the kernel knows about your shell
grep -E 'Name|State|Tgid|^Pid|PPid|Threads|Uid' /proc/$$/status
ls -l /proc/$$/fd # its file descriptors
id # its security context
# Watch the system calls behind a command, and count them
strace -f -e trace=clone,clone3,execve,wait4 bash -c 'ls > /dev/null'
strace -c ls > /dev/null
# Make a zombie for 30 seconds, then see it
python3 -c "import os, time; pid = os.fork(); (os._exit(0) if pid == 0 else time.sleep(30))" &
sleep 1; ps -o pid,ppid,stat,comm --ppid $!
# Read exit codes: 143 and 137
sleep 100 & P=$!; kill -TERM $P; wait $P; echo "exit code $?"
sleep 100 & P=$!; kill -KILL $P; wait $P; echo "exit code $?"
# One thread segfaults and the whole process goes down: the print never runs, exit code 139
python3 -c 'import ctypes, threading, time
threading.Thread(target=lambda: ctypes.string_at(0)).start()
time.sleep(5); print("never printed")'; echo "exit code $?"
Thread Memory ভাগ করে, Process করে না। এটা share.py নামে Save করে চালাও:
import multiprocessing as mp
import threading
counter = 0
def bump():
global counter
counter = 100
if __name__ == '__main__':
t = threading.Thread(target=bump)
t.start(); t.join()
print('after thread :', counter) # 100: same memory
counter = 0
p = mp.Process(target=bump)
p.start(); p.join()
print('after process:', counter) # 0: the child changed its own copy
যে Dockerfile Signal-কে তোমার App পর্যন্ত পৌঁছাতে দেয়:
# Shell form runs under sh, so the app does not get SIGTERM:
# CMD node server.js
# Exec form makes the app PID 1, and it needs its own handler:
CMD ["node", "server.js"]
docker run --init দিয়ে চালাও, তাহলে Signal পৌঁছাবে আর Zombie Reap হবে; তারপর time docker stop <name>। ঠিক ১০ সেকেন্ড লাগলে বুঝবে Signal পৌঁছাচ্ছে না।
#সারসংক্ষেপ
মূল শিক্ষা
- Kernel হলো OS-এর ক্ষমতাধর কেন্দ্র। Program চলে User Mode-এ, আর সবকিছু চায় System Call দিয়ে, যার দাম মোটামুটি ১০০ ns থেকে ১ µs; তাই Program Buffer আর Batch করে।
- Process হলো চলমান Program, যার নিজের Virtual Address Space, PID, FD, Security Context আর Thread আছে, আর Kernel-এর PCB-তে যার হিসাব থাকে। Process-দের মাঝে দেয়াল, তাতে ইচ্ছাকৃত কিছু দরজা।
- Thread হলো Process-এর ভেতরে Execution-এর ধারা। Thread-রা Heap, Global, Code আর FD ভাগ করে; প্রত্যেকের নিজের Stack, Program Counter, Register আর TID। Stack আলাদা, কিন্তু সুরক্ষিত না।
forkCopy-on-Write দিয়ে কপি করে,execনতুন Program বসায়,waitStatus নেয়। Linux-এ Thread হলোclone-এর এমন Task, যে সব ভাগ করে। তৈরির দাম এত যে Server Worker আবার ব্যবহার করে। Thread-ওয়ালা Program-এforkকরে কাজ চালিয়ে যেও না।- অবস্থা: Ready আর Running দুটোই
R,Sসস্তায় অপেক্ষা করে,Dথামানো যায় না, আর CPU অলস অথচ Load বেশি মানে Storage-এ গোলমাল। - Zombie মৃত কিন্তু Reap হয়নি, তাকে মারা যায় না; Parent ঠিক করো। Orphan জীবিত, PID 1 দত্তক নেয়। Container-এ PID 1 তোমার App-ই, তাই একটা init দাও।
- IPC: ধারার জন্য Pipe, লোকালে Unix Socket, গতির জন্য Shared Memory (দাম ভাগের ব্যর্থতা), মেশিন পেরোতে পারে এমন সবকিছুর জন্য TCP।
- Graceful Shutdown-এর জন্য
SIGTERMধরা যায়;SIGKILLযায় না। Exit Code = ১২৮ + Signal: ১৩৭, ১৩৯, ১৪৩। Dockerfile-এ Shell Form, বা Handler-হীন PID 1,SIGTERMগিলে ফেলে। - Bug-এর ক্ষতির সীমা হলো যে তার Memory ভাগ করে: Segfault Process-এর সব Thread মারে। Shared Buffer-এর কারণে একটা Crash-এ PostgreSQL সব Backend Reset করে, MySQL পুরো Process হারায়, আর Chrome Site আলাদা রাখে।
- আসল System মেলায় কয়েকটা Process, Thread-এর Pool বা Event Loop, আর অনেক মেশিন; State থাকে বাইরে।
আগে নিজে উত্তর দাও, তারপর ব্যাখ্যাটা পড়ো। ঠিক হলেও পড়ো, কারণ ভুল Option-গুলো কেন ভুল, সেটাও ওখানে আছে।
নিজেকে যাচাই করোপ্রশ্ন
Program আর Process-এর পার্থক্য সবচেয়ে ভালো বলছে কোনটা?
নিজেকে যাচাই করোপ্রশ্ন
এর মধ্যে কোনটা পুরো Process-এ ভাগ না হয়ে প্রতিটা Thread-এর নিজের?
নিজেকে যাচাই করোপ্রশ্ন
User Mode আর Kernel Mode নিয়ে কোন দুটো কথা ঠিক?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
একটা Logging Library প্রতিটা লাইন আলাদা write System Call-এ লেখে, আর সেকেন্ডে ২,০০,০০০ লাইনে অনেক CPU খায়। সবচেয়ে কার্যকর সমাধান কী?
নিজেকে যাচাই করোপ্রশ্ন
দুটো Process একটা Variable-এর Address Print করল, দুটোতেই 0x7f00a000, কিন্তু সেখানকার মান আলাদা। কেন?
নিজেকে যাচাই করোপ্রশ্ন
C-তে লেখা Server-এর একটা Thread-এর Bug-এ ভুল Pointer দিয়ে লেখা গিয়ে পড়ল আরেকটা Thread-এর Stack-এ। কী হবে?
নিজেকে যাচাই করোপ্রশ্ন
একটা Thread একটা Socket-এ close(7) ডাকল। একই Process-এর আরেকটা Thread FD 7 থেকে পড়তে যাচ্ছিল। ঝুঁকি কী?
নিজেকে যাচাই করোপ্রশ্ন
Parent আর Child-এ fork কী ফেরত দেয়?
নিজেকে যাচাই করোপ্রশ্ন
৪০ GB Memory ব্যবহার করা একটা Parent Process fork ডাকল। কোন কথাটা ঠিক?
নিজেকে যাচাই করোপ্রশ্ন
একটা Child এমন Program-এ execve ডাকল যা নেই, আর Code-এর পরের লাইনে Parent-এর সাধারণ Server Logic। কী ভুল হতে পারে?
নিজেকে যাচাই করোপ্রশ্ন
একটা Multithreaded Python Service exec ছাড়াই পেছনের কাজের জন্য Child Fork করে। মাঝে মাঝে Child প্রথমবার Memory নিতে গিয়েই চিরকাল আটকে যায়। সবচেয়ে সম্ভাব্য কারণ কী?
নিজেকে যাচাই করোপ্রশ্ন
Linux-এর Kernel Thread-কে কীভাবে দেখে?
নিজেকে যাচাই করোপ্রশ্ন
একটা Container-এ pids.max ৫১২। App-এর Heap আর CPU ঠিক আছে, কিন্তু নতুন Request ব্যর্থ হচ্ছে Thread বানানোর Error দিয়ে। কোন দুটো ধাপ সবচেয়ে মানানসই?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
ps-এ একটা Web Thread বেশিরভাগ সময় stat S দেখায়। এটা কী বলছে?
নিজেকে যাচাই করোপ্রশ্ন
৮ Core-এর মেশিনে Load Average ৪০, কিন্তু CPU ৩% ব্যস্ত, আর kill -9 দিয়ে কিছু Process থামে না। সবচেয়ে সম্ভাব্য কী?
নিজেকে যাচাই করোপ্রশ্ন
ps-এ একই Parent-এর নিচে শয়ে শয়ে Process defunct দেখাচ্ছে, আর kill -9-এ কিছুই বদলায় না। ঠিক সমাধান কী?
নিজেকে যাচাই করোপ্রশ্ন
Orphan নিয়ে কোন দুটো কথা ঠিক?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
একটা App init ছাড়া Container-এ চলে। কয়েক সপ্তাহে Zombie জমে জমে নতুন Process আর চালু হয় না। এটা শুধু Container-এই কেন হয়?
নিজেকে যাচাই করোপ্রশ্ন
একই মেশিনের PHP-FPM Pool-এ Nginx-কে বিপুল হারে Request পাঠাতে হবে। কোনটা সবচেয়ে যুক্তিযুক্ত?
নিজেকে যাচাই করোপ্রশ্ন
Pipe-এর তুলনায় Process-দের মধ্যে Shared Memory-র প্রধান ঝুঁকি কী?
নিজেকে যাচাই করোপ্রশ্ন
producer | consumer Pipeline-এ Consumer ধীর, আর Pipe-এর Buffer ভরে গেল। কী হবে? আর তারপর Consumer Crash করলে?
নিজেকে যাচাই করোপ্রশ্ন
Response-এর মাঝখানে Client চলে গেলে একটা Server মাঝে মাঝে কোনো Log ছাড়াই মরে যায়। সবচেয়ে সম্ভাব্য কারণের সমাধান কোনটা?
নিজেকে যাচাই করোপ্রশ্ন
এর মধ্যে কোন Signal Process ধরতে, আটকাতে বা উপেক্ষা করতে পারে না?
নিজেকে যাচাই করোপ্রশ্ন
একটা Kubernetes Pod ১৩৭ Code দিয়ে বের হলো। সবচেয়ে সঠিক অর্থ কোনটা?
নিজেকে যাচাই করোপ্রশ্ন
docker stop-এ প্রতিবার ঠিক ১০ সেকেন্ড লাগে, তারপর Container ১৩৭ দিয়ে বের হয়। কোন দুটো সম্ভাব্য কারণ?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
Native Code-এ Signal Handler সাধারণত শুধু একটা Flag বসিয়েই শেষ করে কেন?
নিজেকে যাচাই করোপ্রশ্ন
Kubernetes Rolling Deploy-এর সময় একটা Server SIGTERM ঠিকই সামলায়, তবু Signal-এর ঠিক পরে কয়েকটা Request ব্যর্থ হয়। সবচেয়ে সম্ভাব্য কারণ কী?
নিজেকে যাচাই করোপ্রশ্ন
একটা Java Service-এ ২০০টা Worker Thread। একটা Thread Native Image Library-র ভেতরে Segfault করল। বাকি ১৯৯টার কী হবে?
নিজেকে যাচাই করোপ্রশ্ন
এর মধ্যে কোনটায় একটা Thread বা Goroutine-এর না-সামলানো Error পুরো Process শেষ করে?
নিজেকে যাচাই করোপ্রশ্ন
একটা PostgreSQL Backend Segfault করল। অন্য যুক্ত Client-রা কী দেখবে?
নিজেকে যাচাই করোপ্রশ্ন
একটা App-এর ৪০টা Instance, প্রত্যেকে PostgreSQL-এ ৫০টা Connection রাখে, আর Database হিমশিম খাচ্ছে। আসল খরচ কমায় কোনটা?
নিজেকে যাচাই করোপ্রশ্ন
তোমাকে Server-এর ভেতরে তৃতীয় পক্ষের Plugin চালাতে হবে, যা Crash করতে পারে বা যাতে Security Bug থাকতে পারে। কোন Design সবচেয়ে মানানসই?
নিজেকে যাচাই করোপ্রশ্ন
Process-এর চেয়ে Thread পছন্দ করার সাধারণ কারণ কোনটা না?