#C یک زبان برنامهنویسی چندسبکی است که از سبکهای مختلف برنامهنویسی مانند رویهای، شیگرا و تابعی پشتیبانی میکند. یکی از ویژگیهای اساسی سی شارپ پشتیبانی آن از چند نخی «Multithreading» است که به توسعهدهندگان این امکان را میدهد تا برنامههایی بنویسند که بتوانند چندین وظیفه را بهطور همزمان انجام دهند.
در این مقاله با مفاهیم پایهای چند نخی در سی شارپ «#Multithreading in C»، مزایا، چالشها، و روشهای مختلف پیادهسازی آن مانند استفاده از کلاسهای Thread، Task، کلمات کلیدی async و await، و کلاس ThreadPool آشنا خواهید شد.
مقدمه
در دنیای امروز که سرعت و عملکرد نرمافزارها نقش مهمی در تجربه کاربر ایفا میکند، بهرهگیری از تکنیکهای پیشرفته برنامهنویسی برای بهبود کارایی بیش از پیش اهمیت یافته است. یکی از این تکنیکها، چند نخی است که به برنامهها این امکان را میدهد تا چندین وظیفه را بهطور همزمان اجرا کنند. زبان برنامهنویسی #C با فراهمسازی ابزارها و کلاسهای متنوع برای پیادهسازی چند نخی، بستری قدرتمند جهت توسعهی برنامههای سریع و پاسخگو فراهم کرده است. اگر به دنبال آن هستید که عملکرد برنامههای خود را به سطح بالاتری ارتقا دهید و از تمام توان پردازنده بهره ببرید، این مقاله نقطهی شروع مناسبی برای شما خواهد بود.
چند نخی در سی شارپ
چند نخی در سی شارپ #C یک تکنیک برنامهنویسی است که اجازه میدهد چندین نخ «Thread» بهطور همزمان در یک فرآیند واحد اجرا شوند. هر نخ نشاندهندهی یک مسیر اجرایی جداگانه است که امکان انجام موازی وظایف را فراهم میکند و باعث بهبود کارایی و عملکرد برنامهها میشود.
مثال: پیادهسازی چند نخی با استفاده از کلاس Thread.
// Creating a thread using the Thread class in C# using System; using System.Threading; class PStore { static void Main() { // create a new thread Thread t = new Thread(Worker); // start the thread t.Start(); // do some other work in the main thread for (int i = 1; i < 5; i++) { Console.WriteLine("Main thread doing some work"); Thread.Sleep(100); } // wait for the worker thread to complete t.Join(); Console.WriteLine("Done"); } static void Worker() { for (int i = 1; i < 3; i++) { Console.WriteLine("Worker thread doing some work"); Thread.Sleep(100); } } }
خروجی
Main thread doing some work Worker thread doing some work Worker thread doing some work Main thread doing some work Main thread doing some work Main thread doing some work Done
توضیح: در مثال بالا، یک نخ جدید ایجاد و اجرا میشود که متد Worker را اجرا میکند، در حالی که نخ اصلی وظیفه خودش را انجام میدهد. هر دو نخ پیامهایی را چاپ میکنند و برای مدت کوتاهی متوقف میشوند (sleep). نخ اصلی منتظر میماند تا نخ Worker کارش را تمام کند، سپس عبارت Done را چاپ میکند.
مثال دنیای واقعی
چندوظیفگی «Multitasking» به معنای اجرای همزمان چندین وظیفه یا فرآیند در یک بازه زمانی مشخص است. سیستمعامل ویندوز نمونهای از چندوظیفگی است، زیرا میتواند بیش از یک فرآیند را بهطور همزمان اجرا کند؛ مانند اجرای همزمان Google Chrome، Notepad، VLC Player و غیره. سیستمعامل برای اجرای این برنامهها بهصورت همزمان از مفهومی به نام فرآیند «Process» استفاده میکند.
فرآیند «Process»: بخشی از سیستمعامل است که مسئول اجرای یک برنامه کاربردی میباشد.
نخ «Thread»: یک نخ فرآیندی سبکوزن است، یا بهعبارت دیگر، نخ واحدی است که کد برنامه را اجرا میکند.
هر برنامهای دارای منطق خاص خود است و نخ مسئول اجرای این منطق میباشد. هر برنامه بهصورت پیشفرض دارای یک نخ است که وظیفه اجرای منطق برنامه را بر عهده دارد، و به این نخ “نخ اصلی” (Main Thread) گفته میشود. بنابراین، هر برنامه یا نرمافزاری بهطور پیشفرض از مدل تکنخی «single-threaded» استفاده میکند.
مدل تکنخی
در مدل تکنخی «Single Threaded Model»، تمام فرآیندهای موجود در برنامه بهصورت هماهنگ و پشت سر هم اجرا میشوند؛ به این معنا که هر فرآیند باید منتظر بماند تا فرآیند قبلی اجرای خود را کامل کند. این موضوع باعث میشود زمان پردازش افزایش یابد.
برای مثال، کلاسی به نام Geek داریم که شامل دو متد مختلف به نامهای method1 و method2 است. در این حالت، نخ اصلی مسئول اجرای تمام این متدها است، بنابراین نخ اصلی این متدها را به ترتیب و یکی پس از دیگری اجرا میکند.
مثال: نمایش مدل تکنخی
// Single threaded model example using System; using System.Threading; public class PStore { public static void method1() { // It prints numbers from 0 to 4 for (int i = 1; i < 5; i++) { Console.WriteLine("Method1 is : {0}", i); if (i == 2) { Thread.Sleep(100); } } } public static void method2() { // It prints numbers from 0 to 4 for (int j = 1; j < 5; j++) { Console.WriteLine("Method2 is : {0}", j); } } } public class Program { static public void Main() { // Calling static methods PStore.method1(); PStore.method2(); } }
خروجی
Method1 is : 1 Method1 is : 2 Method1 is : 3 Method1 is : 4 Method2 is : 1 Method2 is : 2 Method2 is : 3 Method2 is : 4
توضیح: در مثال بالا، متد Method2 منتظر میماند تا متد Method1 بهطور کامل اجرا شود و تا زمانی که اجرای آن تمام نشود، Method2 اجرا نخواهد شد. این یکی از معایب مدل تکنخی است. برای غلبه بر این مشکل، از مدل چند نخی استفاده میشود، که در آن میتوان برنامهها و نخهای مختلف را بهصورت همزمان اجرا کرد.
چند نخی همچنین باعث استفاده بهینهتر از CPU میشود، زیرا چند نخی بر اساس مفهوم اشتراکگذاری زمانی «time-sharing» کار میکند؛ به این معنا که هر نخ زمان مشخصی برای اجرا دارد و اجرای آن تأثیری بر اجرای سایر نخها نمیگذارد. این بازه زمانی توسط سیستمعامل تعیین میشود.
چند نخی به برنامه اجازه میدهد تا نخهای متعددی را بهطور همزمان اجرا کند. این کار میتواند عملکرد و پاسخدهی برنامه را بهطور قابل توجهی بهبود بخشد.
مثال: نمایش چند نخی در #C
// C# program to illustrate the // concept of multithreading using System; using System.Threading; public class PStore { public static void method1() { for (int i = 1; i < 5; i++) { Console.WriteLine("Method1 is : {0}", i); // sleep for 100 milliseconds if (i == 2) { Thread.Sleep(100); } } } public static void method2() { // It prints numbers from 0 to 10 for (int j = 1; j < 5; j++) { Console.WriteLine("Method2 is : {0}", j); } } static public void Main() { // Creating and initializing threads Thread thr1 = new Thread(method1); Thread thr2 = new Thread(method2); thr1.Start(); thr2.Start(); } }
خروجی
Method1 is : 1 Method1 is : 2 Method2 is : 1 Method2 is : 2 Method2 is : 3 Method2 is : 4 Method1 is : 3 Method1 is : 4
توضیح: در مثال بالا، دو نخ به نامهای thr1 و thr2 با استفاده از کلاس Thread ایجاد و مقداردهی اولیه میشوند. سپس با استفاده از دستورات ;()thr1.Start و ;()thr2.Start اجرای هر دو نخ آغاز میشود. اکنون هر دو نخ بهطور همزمان اجرا میشوند و پردازش thr2 وابسته به پردازش thr1 نیست، برخلاف مدل تکنخی.
توجه: خروجی ممکن است به دلیل تغییر زمینه (context-switching) متفاوت باشد.
روشهای پیادهسازی چند نخی
در #C، چند نخی از طریق فضای نام System.Threading پشتیبانی میشود. چندین روش برای ایجاد و مدیریت نخها وجود دارد که در زیر توضیح داده شده است:
- کلاس Thread
- کلاس Task
- Async و Await
- کلاس ThreadPool
۱- کلاس Thread
کلاس Thread سادهترین روش برای پیادهسازی چند نخی در سی شارپ #C است. میتوانیم یک نخ با ایجاد یک شی از کلاس Thread بسازیم و متدی که نمایانگر وظیفهای است که باید اجرا شود را به آن پاس دهیم.
مثال: ایجاد نخها با استفاده از کلاس Thread
// Implementing Multithreading using Thread class using System; using System.Threading; class PStore { static void Main() { Thread thread1 = new Thread(PrintNumbers); // Start the thread thread1.Start(); PrintNumbers(); } static void PrintNumbers() { for (int i = 1; i < 5; i++) { Console.WriteLine(i); Thread.Sleep(100); } } }
خروجی
۱ ۱ ۲ ۲ ۳ ۳ ۴ ۴
توضیح: در مثال بالا، متد PrintNumbers در هر دو نخ، نخ اصلی و نخ جداگانه thread1 اجرا میشود. متد ()Start اجرای نخ را آغاز میکند. این نخ با ایجاد وقفه برای چند ثانیه بین هر تکرار، شبیهسازی کارهایی را انجام میدهد.
۲- کلاس Task
کلاس Task برای وظایف پیچیدهتر یا موازی استفاده میشود که به آنها کتابخانه موازیسازی وظایف (Task Parallel Library یا TPL) نیز گفته میشود. کلاس Task به ما این امکان را میدهد تا وظایفی ایجاد کنیم که بهصورت غیرهمزمان اجرا شوند، که هم عملکرد را بهبود میبخشد و هم خوانایی کد را افزایش میدهد. این کلاس همچنین مدیریت نخها را با استفاده از استخر نخها «thread pooling» و هماهنگسازی «synchronization» سادهتر میکند.
مثال:
// Implmentation of Multithreading in C# using Task Class using System; using System.Threading.Tasks; class PStore { static void Main() { // Running two tasks concurrently Task task1 = Task.Run(() => PrintNumbers()); Task task2 = Task.Run(() => PrintNumbers()); // Wait for both tasks to complete Task.WhenAll(task1, task2).Wait(); } static void PrintNumbers() { for (int i = 0; i < 3; i++) { Console.WriteLine(i); } } }
خروجی
۰ ۱ ۲ ۰ ۱ ۲
توضیح: در مثال بالا، دو وظیفه (Task) با استفاده از ()Task.Run ایجاد شدهاند. این وظایف بهطور همزمان متد PrintNumbers را اجرا میکنند که باعث بهبود عملکرد کلی میشود. متد ()Task.WhenAll اطمینان حاصل میکند که هر دو وظیفه پیش از پایان اجرای برنامه به اتمام رسیدهاند.
۳- Async و Await
روش دیگری برای انجام چندوظیفگی استفاده از وظایف غیرهمزمان «asynchronous tasks» با استفاده از کلمات کلیدی async و await است. این دو برای برنامهنویسی غیرهمزمان بهکار میروند و میتوانند عملیاتهایی که به پردازنده «CPU-bound» یا ورودی/خروجی «I/O-bound» وابسته هستند را با هم ترکیب کنند. این روش چند نخی سنتی محسوب نمیشود اما میتوان آن را با Task.Run ترکیب کرد تا عملیات چند نخی غیرمسدودکننده «non-blocking» انجام شود.
مثال: در تکه کد زیر، ترکیب وظایف با استفاده از async/await برای انجام عملیاتهای غیرهمزمان «asynchronous» در برنامهها نمایش داده شده است. در ابتدا، از نخها «Threads» برای اجرای دو وظیفه بهطور همزمان استفاده میشود و سپس از Task و async/await برای انجام همان وظایف بهصورت غیرهمزمان استفاده میشود.
// Combining tasks with async/await to perform // asynchronous operations using System; using System.Threading; using System.Threading.Tasks; class PStore { static void Main() { // Example using Threads Thread thread1 = new Thread(() => task("Thread 1")); Thread thread2 = new Thread(() => task("Thread 2")); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); Console.WriteLine("moving to task"); // Example using Tasks with async/await Task.Run(async () => await RunAsyncTasks()).Wait(); Console.WriteLine("All tasks completed."); } static void task(string threadName) { for (int i = 1; i <= 2; i++) { Console.WriteLine($"{threadName} print: {i}"); Thread.Sleep(100); // Simulate work } } static async Task RunAsyncTasks() { Task task1 = Task.Run(() => task("Task 1")); Task task2 = Task.Run(() => task("Task 2")); await Task.WhenAll(task1, task2); } }
خروجی
Thread 1 print: 1 Thread 2 print: 1 Thread 1 print: 2 Thread 2 print: 2 moving to task Task 2 print: 1 Task 2 print: 2 Task 1 print: 1 Task 1 print: 2 All tasks completed.
توضیح: در مثال بالا ابتدا، پیامهای Thread 1 و Thread 2 چاپ میشود، که نشاندهنده اجرای نخها بهطور همزمان است. سپس، پیامها برای وظایف Task 1 و Task 2 چاپ میشود که بهطور غیرهمزمان اجرا شدهاند.
در نهایت، پس از اتمام تمام وظایف، پیام “.All tasks completed” چاپ میشود.
۴- کلاس ThreadPool
کلاس ThreadPool یکی از ویژگیهای کاربردی #C است. این کلاس یک مجموعه (pool) از نخها را مدیریت میکند و میتوان از آن برای اجرای وظایف بهصورت غیرهمزمان استفاده کرد. بهجای ایجاد و نابودی نخ برای هر وظیفه که میتواند منابع زیادی مصرف کند، ThreadPool این امکان را فراهم میسازد که چندین وظیفه از تعداد محدودی نخ بهصورت مشترک استفاده کنند.
این رویکرد باعث کاهش سربار (overhead) و افزایش عملکرد میشود، بهویژه در برنامههایی که نیاز به عملیاتهای مکرر و کوتاهمدت دارند.
مثال: نمایش استفاده از کلاس ThreadPool در #C
// Use of ThreadPool Class using System; using System.Threading; class PStore { static void Main() { // queue a work item to the thread pool ThreadPool.QueueUserWorkItem(Worker, "Hello, world!"); // do some other work in the main thread for (int i = 1; i < 5; i++) { Console.WriteLine("Main thread doing some work"); Thread.Sleep(100); } Console.WriteLine("Done"); } static void Worker(object state) { string message = (string)state; for (int i = 1; i < 5; i++) { Console.WriteLine(message); Thread.Sleep(100); } } }
خروجی
Main thread doing some work Hello, world! Hello, world! Main thread doing some work Main thread doing some work Hello, world! Hello, world! Main thread doing some work Done
توضیح: در مثال بالا، متد Worker در یک نخ جداگانه اجرا میشود، در حالی که نخ اصلی در حال انجام کار دیگری است. متد Thread.Sleep برای شبیهسازی انجام برخی کارها در هر دو نخ استفاده شده است.
نکات مهم:
- بنبست «Deadlocks»: اطمینان حاصل کنید که نخها در انتظار منابعی که توسط نخ دیگر نگه داشته شدهاند، گیر نکنند. از کلیدواژه lock با احتیاط استفاده کنید و منابع را به ترتیبی ثابت قفل کنید.
- استفاده از استخر نخها «Thread Pooling»: از ()Task.Run و ThreadPool استفاده کنید تا نیازی به مدیریت دستی نخها نباشد. استفاده از استخر نخ باعث کاهش سربار و بهبود بهرهوری منابع میشود.
- محدودیت زمانی: همیشه مکانیزمی برای لغو وظایف طولانی در نظر بگیرید تا پاسخگویی برنامه (بهویژه در رابطهای کاربری) حفظ شود.
- دادههای مشترک: استفاده از دادههای مشترک بین نخها را به حداقل برسانید تا از شرایط مسابقه (Race Conditions) جلوگیری شود. در صورت نیاز به استفاده از دادههای مشترک، حتماً از همگامسازی مناسب استفاده کنید.
مزایای چند نخی در سی شارپ
از مزایای استفاده از چند نخی (Multithreading) در زبان برنامهنویسی سیشارپ عبارتند از:
- اجرای چندین فرآیند بهصورت همزمان
- استفاده حداکثری از منابع CPU
- اشتراکگذاری زمانی بین فرآیندهای مختلف
- کمک به دستیابی به چندوظیفگی «Multitasking»
در مجموع، استفاده از چند نخی در سیشارپ باعث بهبود کارایی و مقیاسپذیری برنامهها، کاهش زمان تأخیر، و مدیریت بهتر منابع میشود. این ویژگی بهویژه در برنامههای پیچیده و با حجم بالای دادهها، مانند برنامههای شبکهای یا سیستمهای پردازش دادههای بزرگ، اهمیت زیادی دارد.
نتیجهگیری
چند نخی یکی از تکنیکهای قدرتمند در برنامهنویسی #C است که با استفاده صحیح از آن میتوان کارایی، سرعت، و پاسخگویی برنامهها را بهطور چشمگیری افزایش داد. این قابلیت به توسعهدهندگان اجازه میدهد تا وظایف مختلف را بهصورت همزمان اجرا کنند و از منابع سیستم بهویژه CPU حداکثر بهرهبرداری را داشته باشند.
در این مقاله با مفاهیم پایهای چند نخی در سی شارپ آشنا شدیم و روشهای مختلف پیادهسازی آن از جمله استفاده از کلاسهای Thread، Task، کلمات کلیدی async و await و همچنین ThreadPool را بررسی کردیم. همچنین نکات مهمی همچون مدیریت دادههای مشترک، جلوگیری از بنبست، و استفاده صحیح از منابع نیز مطرح شد که برای توسعهی برنامههای پایدار و کارآمد حیاتی هستند.
در نهایت، اگرچه چند نخی میتواند قدرت زیادی به برنامههای ما ببخشد، اما مستلزم دقت و طراحی صحیح است. استفادهی هوشمندانه و اصولی از این قابلیت میتواند برنامههایی سریع، پایدار و مقیاسپذیر ایجاد کند که در دنیای نرمافزار امروزی مزیتی رقابتی محسوب میشود.