بررسی Threading در #C

دوشنبه 6 مهر 1394

در این پست یک بررسی اجمالی در مورد Threading در #C خواهیم پرداخت. Threading یک مجموعه کوچک از دستورات اجرایی می باشد و مجموعه می تواند برای ایزوله کردن یک کار در یک فرآیند استفاده شود. نخ یا Thread، واحد قابل توزیعی از کد است. در محیط چند وظیفه ای مبتنی بر نخ، تمام فرآیندها حداقل یک نخ دارند. ولی می توانند چند نخ داشته باشند. یعنی یک برنامه بطور همزمان میتواند دو یا چند کار را انجام دهد.

بررسی  Threading در #C

برای به دست آوردن موازی و ارائه تعامل متقابل کاربر با برنامه، یکی از موثرترین روش ها پیاده سازی چندنخی (multiple threading) می باشد. taskها و  threadها با هم مرتبط می باشند. یک Task چیزی است که ما میخواهیم انجام دهیم و یک Thread یکی از ایجادکننده یا به اصطلاح کارگرهایی است که Task را انجام می دهد. Threadها اغلب یک فرآیند سبک و شناخته شده می باشند. Dot Net framework دارای فضای نام System.Threading می باشد.

ایجاد یک Thread

در ابتدا مجبوریم که یک تابع call back ایجاد کنیم تا نقطه شروع thread ما باشد. کدهای زیر یک مثال از یک thread ساده می باشد.

class Program  
{  
    //Call back function for Thread  
    public static void MyCallBackFunction()  
    {  
        int i = 0;  
        while (i < 4)   
        {  
            Console.WriteLine("Hello new Thread...");  
            i++;  
        }  
    }  
    static void Main(string[] args)   
    {  
        //Create an object for the Thread  
        Thread myThread = new Thread(new ThreadStart(MyCallBackFunction));  
        //To start a Thread  
        myThread.Start();  
        //To abort a Thraed throwing the ThreadAbortException  
        //myThread.Abort();  
        //To suspant a Thread  
        //myThread.Suspend();  
        //To resume a Thread  
        //myThread.Resume();  
        Console.ReadKey();  
    }  
}

خروجی کدهای بالا

تعامل بین Threadها

بعد از شروع Thread نیازی به متوقف کردن آن نیست. این کار به صورت خودکار توسط Net framework. انجام می شود. در مثال زیر میبینیم که چگونه بین دو Thread که همزمان در یک فرآیند اجرا شده است تعامل وجود دارد. اجرای برنامه با ایجاد یک شی از کلاس ThreadDemo شروع می شود. خصوصیت IsAlive اجازه می دهد تا برنامه تا زمانی که Thread مقداردهی اولیه شده و متد ()Sleep بگوید که Thread از مقدار زمان خودش منصرف شده و همچنین اجرا برای یک کاربر خاص را متوقف می کند، اندازه دوره را میلی ثانیه تعیین کند. بعد از آن Thread متوقف شده و پیوسته می شود. قابلیت پیوستن یک Thread این است که Thread اصلی منتظر منقضی شدن یا به پایان رسیدن آن برای یک زمان مشخص می باشد.

public class ThreadDemo   
{  
    public void Demo()  
    {  
        while (true)   
        {  
            Console.WriteLine("ThreadDemo.Demo is running under own thread..");  
        }  
    }  
};  
  
class Program   
{  
    public static int Main()   
    {  
        Console.WriteLine("Example of Thread Start, Stop and Join..");  
        ThreadDemo oAlpha = new ThreadDemo();  
        // Create the thread object and passing this object in ThreadDemo.Demo method  
        // throughout a ThreadStart delegate. It does not start the thread.  
        Thread oThread = new Thread(new ThreadStart(oAlpha.Demo));  
        oThread.Start();  
        // Spin for a moment waiting for the started thread to become  
        // The Thread will be Alive:  
        while (!oThread.IsAlive);  
        // Main thread is keeping to sleep for 1 millisecond to allow oThread  
        // in order to executre some work  
        Thread.Sleep(1);  
        oThread.Abort();  
        // In this point Wait until oThread is finished. Join also has overloads  
        // that take a millisecond interval.  
        oThread.Join();  
        Console.WriteLine();  
        Console.WriteLine("ThreadDemo.Demo has finished..");  
        try   
        {  
            Console.WriteLine("Trying to restart ThreadDemo.Demo..");  
            oThread.Start();  
        } catch (ThreadStateException)   
        {  
            Console.Write("ThreadStateException restarting ThreadDemo.Demo..");  
            Console.WriteLine("Expected aborted! threads cannot be restarted..");  
        }  
        Console.ReadKey();  
        return 0;  
    }  
}

خروجی در تصویر زیر قابل مشاهده است.

 همگام سازی Nonblocking

یکی از آسان ترین روش ها برای مانع از پرشدن حافظه Full fence می باشد. که مانع از سفارش دادن مجدد دستورات یا کش کردن در محدوده آن می شود که  fence متد Thread.MemoryBarrier  را فراخوانی می کند.

مثال برای  full fence

class Question   
{  
    int answer;  
    bool complete;  
    void APart()   
    {  
        answer = 10;  
        // Thread Barrier 1  
        Thread.MemoryBarrier();  
        complete = true;  
        // Thread Barrier 2  
        Thread.MemoryBarrier();  
    }  
    void BPart()   
    {  
        // Thread Barrier 3  
        Thread.MemoryBarrier();  
        if (complete)   
        {  
            // Thread Barrier 4  
            Thread.MemoryBarrier();  
            Console.WriteLine(answer);  
        }  
    }  
} 

در اینجا، Thread Barriers1 و 4 در کدهای بالا از نوشتن 0 جلوگیری می کند و Thread Barriers2و 3 اطمینان می دهد که اگرB بعد از A  اجرا شود، complete با True ارزیابی می شود.

در هر خواندن و نوشتن نیازی به یک full fence نمی باشد و همچنین برای سه جواب در مثال بالا به چهار fence نیاز است.

class Question   
{  
    int answer1, answer2, answer3;  
    bool complete;  
    void APart()   
    {  
        answer1 = 10;  
        answer2 = 15;  
        answer3 = 20;  
        // Thread Barrier 1  
        Thread.MemoryBarrier();  
        complete = true;  
        // Thread Barrier 2  
        Thread.MemoryBarrier();  
    }  
    void BPart()   
    {  
        // Thread Barrier 3  
        Thread.MemoryBarrier();  
        if (complete)   
        {  
            // Thread Barrier 4  
            Thread.MemoryBarrier();  
            Console.WriteLine(answer1 + answer2 + answer3);  
        }  
    }  
}

کلیدواژه  Volatile

یک راه پیشرفته برای حل این مساله استفاده از کلیدواژه  Volatile برای فیلد complete_ می باشد.

volatile bool _complete;

کامپایلر دستورات را از واژه کلیدی volatile به منظور تولید یک ایجاد fence برای هر خواندن یا Read  به دست آورده و در هر نوشتن یا write آن را آزاد می کند. برنامه Volatile از تعویض یک Write که توسط یک read دنبال می شود جلوگیری نمی کند.

برای مثال اگر متد1 و متد2 به صورت همزمان در threadهای مختلف اجرا شوند، در آنصورت برای هردوی a و b با مقدار 0 پایان می یابند.

class Volatility   
{  
    volatile int p, q;  
    // Executed on one thread  
    void Method1()   
    {  
        // Volatile write (release-fence)  
        p = 1;  
        // Volatile read (acquire-fence)   
        int a = q;...  
    }  
    // Executed on another thread  
    void Method2()  
    {  
        // Volatile  
        write (release-fence)  
        q = 1;  
        // Volatile read (acquire-fence)   
        int b = p;...  
    }  

VolatileRead و Volatile write

متدهای استاتیک ()VolatileRead و ()Volatile write در Thread خواندن یا نوشتن یک متغیر در زمان اجرا، توسط کلیدواژه Volatile تولید می شود. پیاده سازی این دو متد نسبتا ناکارآمد است هرچند از راه full fence ایجاد شوند.

متد Volatile write

public static void VolatileWrite(ref int addr, int val)   
{  
    MemoryBarrier();  
    addr = val;  
}

متد VolatileRead

public static int VolatileRead(ref int addr)  
{  
   int n = addr; MemoryBarrier(); return n;  
}

در انتها می توانید نمونه کدها را از قسمت فایل ضمیمه دانلود کنید.

آموزش سی شارپ

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • C#.net
  • 3k بازدید
  • 3 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید