این مطلب صرفا چکیده و خلاصهای از مطالبی است که خواندهام و برای تثبیت و درک بهتر مفاهیم سعی کردم آنها را در قالب یک پست منتشر کنم. لذا احتمالا ایرادات فنی و حتی غیر فنی در آن وجود داشته باشد. از خوانندهگان ممنون میشوم اگر این ایرادات و اشکالات را به من اطلاع بدهند.
پیشنویس: تا جایی که من گشتم، متاسفانه ترجمه خوبی برای کلمه thread در زبان فارسی وجود ندارد. اما از آنجایی که نوشتن خود کلمه thread در رسمالخط انگلیسی برای نگارنده سخت است و خواندن کلمه ترد در رسمالخط فارسی احتمالا برای خواننده سخت باشد، ترجیح من استفاده از همین ترجمه نیمبند نخ بود. اگر کسی از خوانندهگان ترجمه معقولتری از این کلمه میداند، ممنون میشوم به من اطلاع بدهد.
در نوشتار قبلی تا حد مختصری سعی کردم مفهوم پروسه را تشریح کنم. در این پست قصد دارم تا درمورد نخها و جایگاه آنها در مقایسه با پروسهها در حد سواد خودم صحبت کنم. قبل از شروع صحبت درمورد خود مفهوم نخ، بنظرم بهتر است تفاوت concurrency و paralelism را بررسی کنیم.
parallelism همانطور که از نامش پیداست، یعنی موازی کاری. با داشتن چند پردازنده (حداقل ۲ پردازنده) کامپیوتر میتواند چند پروسه را در آن واحد پیش ببرد. پس با این حساب میتوان نتیجه گرفت روی کامپیوترهای تکپردازندهای (single-processor) موازی کاری ممکن نیست.
concurrency یعنی پیش بردن چند پروسه به صورت نوبتی در بازههای زمانی بسیار کوتاه. یعنی پردازنده بخش کوچکی از پروسه ۱ را انجام میدهد، بعد بخش کوچکی از پروسه ۲ را انجام میدهد. بعد دوباره برمیگردد به پروسه ۱ و بخش دیگری از آن را انجام میدهد. توجه داشته باشید که در این نوع پردازش، در آن واحد بیش از ۱ پروسه پردازش نمیشود. صرف دو پروسه به صورت نوبتی پیش برده میشوند. اما چون در بازههای زمانی بسیار کوتاه هر دو به نوبت اجرا میشوند، احتمالا برای کاربر این تصور به وجود میآید که هر دو پروسه به صورت همزمان اجرا میشوند. واضح است که این نوع از پردازش بر روی پردازندههای تکهستهای هم قابل پیادهسازی است.
همانطور که در پست قبل توضیح داده شد، جابجایی دو پروسه یا همان context switch هزینه زمانی برای پردازنده دارد. همچنین منابع بین دو پروسه کاملا تفکیک شدهاند از یکدیگر بجز در مواقع خاص. لذا ارتباط بین دو پروسه سخت و زمانبر است، و از طرف دیگر هر پروسه بخش نسبتا زیادی از حافظه را اشغال میکند. این دو مسئله باعث شدند که مفهومی به نام نخ یا همان thread وارد دنیای سیستمعامل شود. نخها فرایندهای پردازشی هستند. هر نخ حتما متعقل به یک پروسه هست. همچنین در خیلی از مواقع چندین نخ متعقل به یک پروسه هستند. یا به عبارت دیگر، یک پروسه میتواند، شامل چندین نخ باشد.
نخهای متعقل به پروسه واحد، منابع زیادی را به صورت اشتراکی استفاده میکنند. مانند فایلهای متعقل به آن پروسه و حافظه. اما هر پردازنده در آن واحد توانایی پیشبردن تنها یک نخ را دارد. لذا نخها در مدت زمان بهرهبرداری از پردازنده به صورت اشتراکی عمل نمیکنند. در واقع سیستمعامل در مدت زمانی محدود پردازنده را در اختیار آن پروسه میگذارد. این وظیفه پروسه است که این زمان را بین نخهای خود تقسیم کند. همچنین از آنجایی که در نهایت هر نخ پردازش جداگانهای دارد، پس هر نخ register های خاص خود را در پردازنده لازم دارد و همچنین program counter خاص خود را دارد. این دو مورد هم جزو منابعی بودند که نخها به اشتراک نمیگذارند.
شاید در ابتدا و با این تفاسیر، خواننده نخها را نوعی پروسه کوچکتر فرض کند. البته که این تصور خیلیهم نادرست نیست، اما دقیقهم نیست. شاید تصور بهتر و دقیقتر این باشد که ما اصولا ماهیت پروسهها و نخها را متفاوت بدانیم. به این صورت که پروسه را صرفا اجتماعی از منابع کامپیوتر بدانیم. هر پروسه، صرفا بخشی از حافظه، فضایی از دیسک و تعدادی فایل است. همچنین مدت زمانی از پردازنده و دیگر دستگاههای کامپیوتر مانند کیبورد و مانیتور استفاده میکند. اجتماع این منابع، میشود یک پروسه. و خب واضح است که اجتماع منابع، هیچ پردازشی را به خودی خود به همراه ندارد. در نتیجه، یک پروسه به تنهایی هیچکاری را از پیش نمیبرد. چیزی که ماهیت پردازشی دارد، همان نخ است. نخ یک فرایند پردازشی، زیر مجموعه یک پروسه است، که از منابع پروسه در راستای اجرا برنامه و پیشبردن پردازش خود استفاده میکند. با این تعبیر، همه پروسهها باید حداقل ۱ نخ داشته باشند. تصویر زیر تا حدی این موضوع را به نمایش میگذارد.
سوالی که الان ممکن است برای خواننده پیش بیاید، این است که وقتی میتوانیم یک پروسه جدید همراه با یک نخ، تولید کنیم، چه لزومی دارد به پروسه قبلی نخهای بیشتری اضافه کنیم؟
در واقع همیشه این الزام وجود ندارد که به پروسه قبلی نخ اضافه کنیم. اضافه کردن پروسه در مقابل اضافه کردن نخ جدید، یک معامله است. هر کدام معایب و محاسنی دارند. به این معایب و محاسن در ادامه بیشتر میپردازیم.
مزایای استفاده از نخهای بیشتر در یک پروسه
همانطور که در تصویر بالا نشان داده شده است، نخهای متعلق به پروسه واحد، بخش program code و heap مشترک دارند. این باعث میشود استفاده از نخهای بیشتر در مقابل پروسههای بیشتر به صرفهتر باشد. در مثال قبل، فرض کنید یک مرورگر بجای نخهای متعدد، از پروسههای متعدد استفاده کند. این یعنی به ازاء هر پروسه، کل برنامه مرورگر در حافظه بارگذاری شود، که خب طبیعتا به صرفه نیست.
معایب استفاده از نخهای بیشتر در یک پروسه
همانطور که گفته شد، یکی از معایب استفاده از نخهای زیاد، نداشتن حافظه اختصاصی برای هر نخ است. این باعث میشود نخها امکان خراب کردن اطلاعات دیگر نخها را داشته باشند. برای اینکه این مشکل تا حدی حل شود، حافظهای با نام Thread local storage ابداع شد. این حافظه در واقع حافظه اختصاصی هر نخ محسوب میشود که دیگر نخها به آن دسترسی ندارند. اگرچه این ابداع، تا حدی مشکل ما را حل میکند، اما بازهم باید توجه داشت، که حافظه TLS خیلی محدود و کوچک است.
مدل استخر نخها برای استفاده بهینهتر از نخها
(چقدر نخ ترجمه مزخرفیه!)
ایجاد یک نخ جدید، برای کامپیوتر هزینه دارد. اختصاص دادن یک حافظه اختصاصی و همچنین اضافه کردن اطلاعات نخ جدید به Process control block پروسه مادر و بسیاری دیگر از جزئیات آن زمانبر هستند. برای همین یک استراتژی برای استفاده از نخهای زیاد ایجاد یک استخری (یا در واقع مخزنی) از نخهاست. فرض کنید یک سرور دارید. به ازاء هر درخواستی که از بیرون میاید، این سرور قرار است یک نخ جدید ایجاد کند و نخ جدید سعی کند درخواست را رتق و فتق کند. اینکار با توجه به سربار بالای ایجاد یک نخ جدید احتمالا خیلی منطقی نباشد. کاری که شاید بهتر باشد این است که در ابتدا تعداد زیادی نخ ایجاد کنیم، هرگاه درخواستی از بیرون آمد، یکی از این نخها مشغول بررسی و پاسخ دادن به آن درخواست میشود. در واقع کار ایجاد یک نخ جدید، در ابتدا برنامه انجام شده، و فقط باید یک نخ را صدا زد.