برنامه نویسی

[FR] چند رشته و برنامه نویسی ناهمزمان در .NET: درک پایه ها و جلوگیری از تله

برنامه نویسی رقابتی برای بهبود عملکرد و پاسخگویی برنامه های مدرن ضروری است. در .NET ، ما می توانیم به لطف چند رشته ای ، کارها را به صورت موازی انجام دهیم و عملیات ناهمزمان را از طریق ASYNC/AWAIT مدیریت کنیم.

در این مقاله بررسی خواهیم کرد:

  • تفاوت بین چند رشته و async
  • کلاس Threadبا Task ولایت ThreadPool
  • مفاهیم بخش های بحرانی ، بن بست و mutex
  • روشهای خوب برای جلوگیری از خطاهای فعلی

چند رشته

چند رشته چیست؟ MultitHreading به این واقعیت اشاره دارد که قادر به اجرای چندین دستورالعمل در مورد همزمان/موازی ، بلکه برای تبادل داده ها بین موضوعات است. MultitHreading می تواند به ویژه برای مدیریت حجم زیادی از داده ها ، برنامه های زمان واقعی ، به روزرسانی قیمت های بازار سهام یا حتی نظارت بر معاملات مالی بازار ضروری باشد.

همزمان و ناهمزمان

غالباً ، چندتایی با مفهوم روش ناهمزمان اشتباه گرفته می شود. با این حال ، این دو رویکرد به موضوعات مختلف پاسخ می دهند. در C#، یک روش ناهمزمان دستورالعمل های خود را بدون مسدود کردن موضوع فراخوانی اجرا می کند و به برنامه اجازه می دهد اجرای خود را ادامه دهد در حالی که یک کار در پس زمینه به پایان می رسد. بر خلاف یک روش همزمان ، که دستورالعمل های یک به یک را به ترتیب انجام می دهد ، یک روش ناهمزمان می تواند چندین عملیات را به صورت موازی شروع کند و نتیجه خود را یک بار در دسترس قرار دهد. او معمولاً باز می گردد Task یا Task، که نشان دهنده وعده نتیجه آینده است ، اجازه استفاده از آن await تا زمان اتمام کار به طور موقت اعدام را به حالت تعلیق درآورد.

موضوع و فرآیند

اکنون که تفاوت بین روشهای چند رشته ای و روشهای ناهمزمان را مشاهده کرده ایم ، علاقه مند خواهیم شد که چه چیزی یک موضوع را از یک فرآیند متمایز می کند. یک فرآیند یک برنامه اجرا است ، دستورالعمل های برنامه را اجرا می کند و دارای یک فضای RAM (RAM) برای حاوی پشته است. با این حال ، یک موضوع بخشی از یک فرآیند است. یک فرآیند می تواند حاوی چندین موضوع باشد که فضای حافظه یکسانی را به اشتراک می گذارند اما باتری اجرای خود را دارند.

منبع: Github: Yavuzhan-Baykara

موضوع و کار

در C#، دو روش برای انجام چند رشته وجود دارد: Thread ولایت Taskبشر

  • Task یک کلاس C# است که به شما امکان می دهد کاری را ایجاد کنید که به روشی ناهمزمان انجام شود. خلاف Threadبا Task موضوعات جدیدی را ایجاد نکنید اما به دنبال یک موضوع موجود در استخر نخ خواهید بود. Task همچنین به شما امکان می دهد بررسی کنید که آیا یک کار کامل است و یک مقدار را برگردانید. Task.Delay() منابع CPU را مصرف نمی کند ، به سادگی یک تایمر را راه اندازی می کند ، در حالی که Task.Run() یک کد را به طور جداگانه اجرا می کند. به طور خلاصه ، Task انتزاعی است که به شما امکان می دهد بدون مدیریت مستقیم موضوعات زیرین ، برنامه ریزی و اجرای کد را انجام دهید.

  • Thread کلاس است که در یک زمینه مستقل از موضوع اصلی برنامه ، یک بلوک دستورالعمل را انجام می دهد. این منطقه حافظه خاص خود (پشته) است ، در حالی که پشته بین تمام موضوعات فرآیند به اشتراک گذاشته می شود. Thread سطح پایین تر از Task و از نخ های استخر استفاده مجدد نمی کند بلکه موارد جدیدی را ایجاد می کند که می تواند تأثیر آن بر عملکرد داشته باشد.

// on creer un nouvelle obj ThreadStart pour une methode qui ne prend pas d'argument
Thread t = new Thread(new ThreadStart(function));
// ou
Thread t = new Thread(function);
t.Start();

// pour une methode qui prend un argument on vas creer un delegate : public  delegate  void  ParameterizedThreadStart(object obj)
Thread t = new Thread(function);
t.Start(parametre);

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

همچنین می توانید از طریق کدی که اجرا می کند ، تعداد مشخصی از اطلاعات را در مورد موضوع فعلی داشته باشید ، اما همچنین استخرهای نخ را ایجاد کرده و در پس زمینه راه اندازی کنید.

public static void Main() {
    ThreadPool.QueueUserWorkItem(ShowThreadInformation);
    var th1 = new Thread(ShowThreadInformation);
    th1.Start();
    var th2 = new Thread(ShowThreadInformation);
    th2.IsBackground = true; th2.Start();
    Thread.Sleep(500); ShowThreadInformation(null);
}

private static void ShowThreadInformation(Object state) {
    lock (obj) {
        var th = Thread.CurrentThread;
        Console.WriteLine("Managed thread #{0}: ", th.ManagedThreadId);
        Console.WriteLine(" Background thread: {0}", th.IsBackground);
        Console.WriteLine(" Thread pool thread: {0}", th.IsThreadPoolThread);
        Console.WriteLine(" Priority: {0}", th.Priority); Console.WriteLine(" Culture: {0}", th.CurrentCulture.Name);
        Console.WriteLine(" UI culture: {0}", th.CurrentUICulture.Name);
        Console.WriteLine();
    }
}

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بخش بحرانی

هنگامی که شما چند رشته را انجام می دهید ، ممکن است اتفاق بیفتد که چندین موضوع به دلایلی که می خواهیم در بقیه این مقاله به تفصیل بپردازیم ، نمی خواهند به یک منبع همزمان دسترسی پیدا کنند. به این یک بخش مهم گفته می شود ، یعنی بخشی از کد که در آن نمی تواند بیش از یک موضوع به طور همزمان وجود داشته باشد. شما می توانید این کار را با استفاده از Mutex ، که یک کلاس برای همگام سازی دسترسی به یک منبع محافظت شده با ممنوعیت دسترسی به موضوعات دیگر است ، اطمینان حاصل کنید.

private static int activeConnections = 0; // Ressource partagée
private static object lockObject = new object(); // Verrou pour la section critique
Thread[] threads = new Thread[5];

public static void Connect()
{
    lock (lockObject) // Section critique
    {
        activeConnections++; // Mise à jour protégée
        Console.WriteLine($"Nouvelle connexion. Connexions actives : {activeConnections}");
    }

    // Simule une connexion active
    Thread.Sleep(1000);

    lock (lockObject) // Section critique
    {
        activeConnections--; // Mise à jour protégée
        Console.WriteLine($"Déconnexion. Connexions actives : {activeConnections}");
    }
}

for (int i = 0; i < threads.Length; i++) {
    threads[i] = new Thread(Connect);
    threads[i].Start();
}

foreach (Thread t in threads) {
    t.Join();
}    
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در این مثال که اتصال به یک سرور را شبیه سازی می کند ، می بینیم که اگر چندین موضوع یک متغیر مشترک را بدون هماهنگ سازی به روز کنند ، این می تواند باعث ایجاد خطاهای شمارش شود (شرایط نژاد).

بن بست / معیشت

با این حال ، بخش های بحرانی باید با دقت مورد استفاده قرار گیرند زیرا می توانند مشکلاتی مانند بن بست یا interfocage ایجاد کنند ، این وضعیتی است که در آن دو موضوع از یکدیگر انتظار دارند. برای درک بهتر چگونگی وقوع آن ، مثال بزنید.

static object lock1 = new object();
static object lock2 = new object();

static void Thread1()
{
    lock (lock1) // Verrouillage de lock1
    {
        Thread.Sleep(100); // Simule un délai
        lock (lock2) // Attente de lock2 (bloqué par Thread2)
        {
            Console.WriteLine("Thread 1 a les deux verrous");
        }
    }
}

static void Thread2()
{
    lock (lock2) // Verrouillage de lock2
    {
        Thread.Sleep(100); // Simule un délai
        lock (lock1) // Attente de lock1 (bloqué par Thread1)
        {
            Console.WriteLine("Thread 2 a les deux verrous");
        }
    }
}

Thread t1 = new Thread(Thread1);
Thread t2 = new Thread(Thread2);

t1.Start();
t2.Start();
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

می بینیم که دو موضوع در تلاشند به دو قفل دسترسی پیدا کنند (lock1 ولایت lock2) به ترتیب معکوس ، انتظارات نامتناهی را ایجاد می کند. برای جلوگیری از این مشکل ، همیشه منابع را به همان ترتیب قفل کنید یا استفاده کنید Monitor.TryEnter با یک زمان اما این عمل نیز در معرض خطر است ، زیرا آزاد کردن نخ خیلی زود می تواند باعث ایجاد یک معیشت شود ، یعنی می توان گفت که دو موضوع سعی می کنند با استراحت سریع قفل در صورت عدم دسترسی ، از درگیری جلوگیری کنند. اما همانطور که همزمان انجام می دهند ، هرگز پیشرفت نمی کنند.

static object lock1 = new object();
static object lock2 = new object();

static void Thread1()
{
    while (true)
    {
        bool gotLock1 = false;
        bool gotLock2 = false;

        try
        {
            gotLock1 = Monitor.TryEnter(lock1);
            gotLock2 = Monitor.TryEnter(lock2);

            if (gotLock1 && gotLock2)
            {
                Console.WriteLine("Thread 1 : Exécute la section critique.");
                break; // Sortie si les deux verrous sont acquis
            }
        }
        finally
        {
            if (gotLock1) Monitor.Exit(lock1);
            if (gotLock2) Monitor.Exit(lock2);
        }

        Console.WriteLine("Thread 1 : Évite le blocage, retente...");
        Thread.Sleep(50); // Sans délai aléatoire, LiveLock possible
    }
}

static void Thread2()
{
    while (true)
    {
        bool gotLock1 = false;
        bool gotLock2 = false;

        try
        {
            gotLock2 = Monitor.TryEnter(lock2);
            gotLock1 = Monitor.TryEnter(lock1);

            if (gotLock1 && gotLock2)
            {
                Console.WriteLine("Thread 2 : Exécute la section critique.");
                break;
            }
        }
        finally
        {
            if (gotLock1) Monitor.Exit(lock1);
            if (gotLock2) Monitor.Exit(lock2);
        }

        Console.WriteLine("Thread 2 : Évite le blocage, retente...");
        Thread.Sleep(50); // Sans délai aléatoire, LiveLock possible
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

اشیاء قابل تغییر / تغییر ناپذیر

یک شیء قابل تغییر ، شیء است که می تواند پس از ایجاد آن اصلاح شود ، برخلاف یک شیء تغییر ناپذیر که وضعیت آن نمی تواند تغییر کند. برای اصلاح یک مقدار در یک شیء غیرقابل تغییر ، باید به جای تغییر شی موجود ، یک بدن جدید با مقدار جدید ایجاد کنید. این همچنین می تواند از به اشتراک گذاری مشکلات در چند رشته ای جلوگیری کند.

تماس مسدود کننده

جدا از مشکلات اشتراک منابع ، می توان یک موضوع را از طریق آنچه که یک تماس مسدود کننده نامیده می شود مسدود کرد. یک تماس مسدود کننده هنگامی اتفاق می افتد که یک نخ عملیاتی را انجام می دهد که تا زمانی که عملیات به پایان نرسد ، مانع از ادامه آن می شود. این می تواند یک تماس سیستم ، دسترسی به شبکه ، پخش فایل یا قفل باشد. به عنوان مثال ، عملکرد read() در C ، اجرای برنامه تا زمانی که داده ها در دسترس نباشند ، اجرای برنامه را مسدود می کند.

نمونه های متداول تماس های مسدود کننده:

  • خواندن پرونده: read()با File.ReadAllBytes() در C#
  • دسترسی به شبکه: Socket.Receive()با HttpClient.Send() (sans async)
  • verroars: lockبا Monitor.Enter()با Mutex.WaitOne()
  • ورودی کاربر: Console.ReadLine()

مایه

ما تاکنون دیده ایم که می توانیم از طریق یک بلوک از دستورالعمل ها را مسدود کنیم lock، اما روش های دیگری مانند وجود دارد Mutexبشر Mutex یک کلاس از C #NET است که دسترسی به یک بخش محافظت شده را مسدود می کند و به یک موضوع واحد اجازه می دهد تا به آن دسترسی پیدا کند ، و دیگران را در ورودی ورودی مسدود کند Mutexبشر وت Mutex بازگشت به استفاده lockبشر ما شروع بخش محافظت شده را با قرار دادن تعریف می کنیم "MutexInstance".WaitOne()، و پایان با قرار دادن "MutexInstance".ReleaseMutex()بشر

public class testMutex {
    private  static Mutex mut = new Mutex();

    public void CreatThred() {
        for (int i = 0; i < number_of_thread; i++) {
            Thread newThread = new Thread(new ThreadStart(methodName));
            newThread.Start();
        }
    }

    public void methodName() {
        // before protected section
        mut.WaitOne();
        // protected section
        mut.ReleaseMutex();
        // end of protected section
    }
}

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ما همچنین می توانیم استفاده کنیم WaitOne() برای تنظیم یک زمان بندی که اگر بخش محافظ زمان بیشتری از آنچه نشان می دهد (در MS) طول می کشد ، نادرست را برمی گرداند ، و نخ که به این بخش حمله می کند ، Mutex را ترک نمی کند و بنابراین تماس نمی گیرد ReleaseMutex() چه کسی فقط با آج که وارد می شود تماس می گیرد.

public class testMutex {
    private  static Mutex mut = new Mutex();

    public void CreatThred() {
        for (int i = 0; i < number_of_thread; i++) {
            Thread newThread = new Thread(new ThreadStart(methodName));
            newThread.Start();
        }
    }

    public void methodName() {
        // before protected section
        if (mut.WaitOne(1000)) {
            // protected section
            mut.ReleaseMutex();
            // end of protected section
        } else {
            // timeout
        }
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سماجت

تاکنون ، ما دیده ایم که چگونه با اجازه دسترسی به یک موضوع واحد ، دسترسی به یک منبع را مسدود کنیم ، اما می توان تعداد موضوعاتی را که می توانند به طور همزمان به آن دسترسی پیدا کنند ، محدود کرد. برای انجام این کار ، ما از یک semaphore استفاده می کنیم ، که به عنوان پیشخوان عمل می کند که حداکثر تعداد موضوعاتی را کنترل می کند که می تواند یک بخش مهم را وارد کند. هنگامی که یک موضوع می خواهد به منبع دسترسی داشته باشد ، باید با فراخوانی این روش منتظر بماند تا مکانی در دسترس باشد Wait() یا WaitAsync()بشر

پس از اتمام کار او ، او دسترسی را آزاد می کند Release()، اجازه می دهد تا موضوع دیگری وارد شود. این رویکرد برای محدود کردن دسترسی رقابتی به یک پایگاه داده ، تنظیم تماس با API یا مدیریت استخر اتصالات مفید است. بر خلاف یک قفل کلاسیک که تمام موضوعات دیگر را مستثنی می کند ، یک semaphore به تعدادی از موضوعات تعریف شده در طول اولیه سازی خود اجازه می دهد ، بنابراین کنترل و موازی بودن را متعادل می کند.

static SemaphoreSlim semaphore = new SemaphoreSlim(3); // Max 3 requêtes en parallèle
static HttpClient client = new HttpClient();

static async Task FetchData(int id)
{
    await semaphore.WaitAsync(); // Attend si le quota est atteint
    try
    {
        Console.WriteLine($"Requête {id} envoyée...");
        var response = await client.GetStringAsync("https://api.example.com/data");
        Console.WriteLine($"Requête {id} terminée.");
    }
    finally
    {
        semaphore.Release(); // Libère une place pour un autre thread
    }
}

static async Task Main()
{
    Task[] tasks = new Task[10];
    for (int i = 0; i < 10; i++)
    {
        tasks[i] = FetchData(i);
    }

    await Task.WhenAll(tasks); // Attendre toutes les tâches
    Console.WriteLine("Toutes les requêtes sont terminées.");
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در این مثال می توانیم ببینیم که باید 10 تماس HTTP برقرار کنیم ، اما برای جلوگیری از اضافه بار API ، 3 تماس همزمان را محدود می کنیم.

نتیجه گیری و شیوه های خوب

برنامه نویسی چند رشته ای و برنامه نویسی ناهمزمان ابزاری قدرتمند برای بهبود پاسخگویی و عملکرد برنامه های .NET هستند ، اما باید از آنها با احتیاط استفاده شود. مدیریت مالت دسترسی به رقیب می تواند باعث خطایی مانند شرایط ، بن بست یا مصرف بیش از حد منابع شود. بنابراین درک زمان استفاده ضروری است Taskبا Thread یا SemaphoreSlim برای کنترل تعداد موضوعات همزمان. با اتخاذ شیوه های خوب مانند استفاده از قفل ها (lockبا Mutexبا Semaphore) ، رعایت قوانین ناپذیری یا حتی مدیریت مسدود کردن تماس ها ، می توانیم بدون تحمل تله ها از چند رشته ای استفاده کنیم.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا