چند نخی در C#
احتمال خیلی زیاد تا به حال با کلمه چندنخی یا Multithreading در C# یا در زبان های برنامه نویسی دیگر مواجه شدید.
زمانی که در حال یادگیری این مفهوم از طریق مقالات و مستندات بودم اکثر مقالات نمی تونستند تفاوتی که چند نخی ایجاد میکنه رو توضیح بدن. در واقع بعضی از نویسندگان طوری توضیح داده بودن که تفاوتی با asynchronous نداشت. من جایی توضیحی پیدا نکردم که تفاوت های Parallel Programming, multithreading, asynchronous رو توضیح بده و این ها باعث گیج شدن ما میشن.
در این مقاله تلاش دارم به روش ساده ای این ها رو توضیح بدم. چند نخی چیه، چرا ازش استفاده میکنیم و در چه موقعیت هایی کاربردی است. همچنین آیا با asynchronous متفاوت است یا خیر، و در نهایت مثال هایی از ساختار چندنخی نوشته میشه.
Thread چیست؟
در ویرایشگر word بخشی که چند دقیقه یک بار مقاله رو به طور خودکار ذخیره می کنه یک thread است (یعنی thread روشی برای اجرای وظیفه است)
Muti Thread چیست؟
در زمانی که ویرایشگر word در حال ذخیره سازی سند است، Gramerly در پشت صحنه در حال کار است تا اشتباهات املایی جملات را پیدا کند، به اجرای همزمان دو وظیفه چند نخی گفته می شود (داشتن بیش از یک اجرا)
MultiThreading چیست؟
استفاده از چند نخ با اجرای همزمان که روی انجام وظایف مختلف کار میکنند را چند نخی می گوییم.
کاربرد چند نخی چیست؟
نخ با انجام کارها به صورت برنامه نویسی موازی در بهبود کارایی برنامه به ما کمک می کند.
برنامه نویسی موازی چیست؟
جریان برنامه است که برنامه به بخش هایی شکسته شده و برای هر بخش CPU اختصاص می یابد که به طور همزمان به اجرای وظایف بپردازد. لذا، با تقسیم بندی این بخش ها زمان اختصاص یافته به هر برنامه کاهش می یابد و باعث افزایش کارایی برنامه خواهد شد.
آیا برنامه نویسی موازی و Asynchronous یکی هستند؟
Asynchronous یک روش انجام کارها به صورت non-blocking است، یعنی بدون انتظار برای یک event اجرا می شود و می شه به اجرای بقیه کارها ادامه داد.
تفاوت این است که Parallel وابسته به سخت افزار است ولی asynchronous در رابطه با Task ها است و چند نخی در رابطه با worker ها است.
نکته
C# از اجرای موازی کد با کمک چند نخی پشتیبانی می ند (یعنی چند نخی روش موازی اجرا است)
ساختار چند نخی
کلمه کلیدی Thread یک کلاس در فضای نامی System.Threading است.
Thread چگونه ایجاد می شود؟
برای پیاده سازی Thread نیاز داریم از فضای نامی threading در برنامه استفاده کنیم و کلاس Thread داخل این فضای نامی است، می توانیم یک شی Thread ایجاد کنیم و این اشیا thread در برنامه thread ایجاد می کنند.
Tread چطور کار می کند؟
در چند نخی، زمان بند thread از زمان بند سیستم عامل برای زمان بندی نخ ها کمک می گیرد تا بتواند به هر thread زمان اختصاص دهد.
در یک ماشین تک پردازشگر، فقط یک thread در آن واحد اجرا می شود، برای یک پردازشگر دو هسته ای می توانیم 4 thread داشته باشیم و برای یک پردازشگر quad-core میتونیم 8 thread داشته باشیم. مدیریت نامناسب ممکن است روی یک ماشین تک پردازشگر چندین thread ایجاد کند و منجر به گلوگاه (bottleneck) منابع شود.
مثالی از چند نخی
using System;
using System.Threading;
class Program {
public static void Main() {
Thread ThreadObject1 = new Thread(Example1); //Creating the Thread
Thread ThreadObject2 = new Thread(Example2);
ThreadObject1.Start(); //Starting the Thread
ThreadObject2.Start();
}
static void Example1() {
Console.WriteLine("Thread1 Started");
for (int i = 0; i <= 5; i++) {
Console.WriteLine("Thread1 Executing");
Thread.Sleep(5000); //Sleep is used to pause a thread and 5000 is MilliSeconds that means 5 Seconds
}
}
static void Example2() {
Console.WriteLine("Thread2 Started");
for (int i = 0; i <= 5; i++) {
Console.WriteLine("Thread2 Executing");
Thread.Sleep(5000);
}
}
}
در مثال فوق بین MainThread و Worker Thread تمایز ایجاد کردیم
MainThread چیست؟
Thread ی که در یک پردازش اول اجرا می شود.
پردازش یا Process در چندنخی به چه معنی است؟
برای اجرای برنامه به منابع ارائه شده توسط این پردازش نیاز داریم. هر پردازش با یک thread واحد به نام primary thread شروع می شود. این پردازش قادر است تا thread های اضافی از thread که در اختیار دارد ایجاد کند.
Synchronization چیست؟
اجرای همروند دو تا چند thread.
چرا نیاز به synchronization داریم؟
چند نخی از تداخل thread ها جلوگیری می کند، این تداخل ها زمانی رخ می دهد که به طور موازی تلاش دارند متغیرهایی را تغییر دهند. Synchronous تضمین می کند که فقط یک پردازش یا thread به بخش حیاتی برنامه دسترسی دارد.
چطور synchronization را مدیریت کنیم؟
برای هندل این موضوع ما چندین روش داریم که به ۴ دسته تقسیم می شوند:
Blocking Methods
Locking Constructs
No blocking Synchronization
Signalling
Join
در thread حالت synchronization join مکانیزم blocking است که به توقف فراخوانی thread کمک می کند. تا زمانی که اجرای thread ی که join روی آن فراخوانی شده است اجرای متوقف می شود.
تفاوت استفاده از join و عدم استفاده از آن در مثال زیر توضیح داده شده.
using System;
using System.Threading;
class Program {
public static void Main() {
//Creating the WorkerThread with the help of Thread class.
Thread ThreadObject1 = new Thread(WorkerThread);
ThreadObject1.Start(); //Starting the Thread
//ThreadObject1.Join(); //Using Join to block the current Thread
Console.WriteLine("1. MainThread Started");
for (int i = 0; i <= 3; i++) {
Console.WriteLine("-> MainThread Executing");
Thread.Sleep(3000); //Here 5000 is 5000 Milli Seconds means 5 Seconds
}
// We are calling the Name of Current running Thread using CurrentThread
Thread Th = Thread.CurrentThread;
Th.Name = "Main Thread";
Console.WriteLine("\nGetting the Name of Currently running Thread");
//Name Property is used to get the name of the current Thread
Console.WriteLine($ "Current Thread Name is: {Th.Name}");
//Priority Property is used to display the Priority of current Thread
Console.WriteLine($ "Current Thread Priority is: {Th.Priority}");
}
static void WorkerThread() {
Console.WriteLine("2. WorkerThread Started");
for (int i = 0; i <= 3; i++) {
Console.WriteLine("-> WorkerThread Executing");
Console.WriteLine("Child Thread Paused");
//Sleep method is used to pause the Thread for a specific period
Thread.Sleep(3000);
Console.WriteLine("Child Thread Resumed");
}
}
}
خروجی با استفاده از join
خروجی بدون استفاده از join
Lock
Lock به قفل کردن thread جاری کمک می کند به طوریکه thread دیگری نمی تواند در اجرا وقفه ای ایجاد کند. باز کردن قفل بعد از پایان اجرا رخ می دهد.
کد مثال زیر lock synchronization method را توضیح می دهد
using System;
using System.Threading;
namespace LockDemo {
class LockExample1 {
//Creating a normal Method to Display Names
public void Display() {
//Lock is used to lock-in the Current Thread
lock(this) {
for (int i = 0; i <= 3; i++) {
Thread.Sleep(3000);
Console.WriteLine($ "My Name is Abhishek{i}");
}
}
}
}
class Example2 {
public static void Main(string[] args) {
//Creating object for LockExample1 Class as _locker so that we can access its Display Method
LockExample1 _locker = new LockExample1();
Console.WriteLine("Threading with the help of Lock");
//Calling the Display Method using ThreadStart Delegate which is supplied to Thread constructor.
Thread t1 = new Thread(new ThreadStart(_locker.Display));
Thread t2 = new Thread(new ThreadStart(_locker.Display));
t1.Start(); //Starting Thread1
t2.Start(); //Starting Thread2
}
}
}
خروجی- Threading بهمراه Lock
خروجی - Threading بدون Lock
Deadlocks
در Deadlock دو thread منتظر resource ی هستند که نیاز دارند اما این resource توسط دیگری نگه داشته شده و باعث می شود هیچ یک از آنها نتوانند به کار ادامه بدن. Lock1 منابع Lock2 رو داره و Lock2 منابع Lock1 رو داره و سردرگمی پیش میارن، لذا هیچ راهی به جز متوقف کردن این وضعیت deadlock نداریم.
برنامه مثال برای Deadlock
using System;
using System.Threading;
namespace DeadlocksDemo {
public class Locking {
static readonly object Lock1 = new object();
static readonly object Lock2 = new object();
static void Display() {
Console.WriteLine("Trying to lock Locker1");
lock(Lock1) {
Console.WriteLine("Locked Locker1");
Thread.Sleep(1000);
Console.WriteLine("Locking Locker2");
lock(Lock2) {
Console.WriteLine("Locked Locker2");
}
Console.WriteLine("Released Locker2");
}
Console.WriteLine("Released Locker1");
}
static void Main() {
new Thread(new ThreadStart(Display)).Start();
Thread.Sleep(1000);
Console.WriteLine("Trying to lock Locker2");
lock(Lock2) {
Console.WriteLine("Locked Locker2");
Console.WriteLine("Locking Locker1");
lock(Lock1) {
Console.WriteLine("Locked Locker1");
}
Console.WriteLine("Released Locker1");
}
Console.WriteLine("Released Locker2");
Console.Read();
}
}
}
خروجی
منبع:
https://www.c-sharpcorner.com/article/multithreading-in-c-sharp/