ایران سرفراز- نرم افزار وپروژهای دانشجویی


نرم افزار وپروژهای دانشجویی

فصل بیست و چهارم ریسمان ها و همگام سازی #C

<!-- /* Font Definitions */ @font-face {font-family:"Cambria Math"; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:1; mso-generic-font-family:roman; mso-font-format:other; mso-font-pitch:variable; mso-font-signature:0 0 0 0 0 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:"Times New Roman","serif"; mso-fareast-font-family:"Times New Roman";} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; font-size:10.0pt; mso-ansi-font-size:10.0pt; mso-bidi-font-size:10.0pt;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:35.4pt; mso-footer-margin:35.4pt; mso-paper-source:0; mso-gutter-direction:rtl;} div.Section1 {page:Section1;} -->

            فصل بیست و چهارم

C# ریسمان ها و همگام سازی

آنچه که در این فصل یاد خواهید گرفت:

آشنایی با برنامههای چندوظیفهگی و نحوهی برنامهنویسی آنها

آشنایی با برنامهنویسی چندریسمانی

همگامسازی ریسمانها

قفل کردن منبع مختلف برای جلوگیری از دسترسی مخرب ریسمانهای همگام

کار با مانیتورها

یک مجموعه از System.Threading ریسمانها مسئول چند وظیف ه گی یک برنام ه ی کاربردی واحد هستن د . فضای نامی

کلاس ها و واس ط ها را برای مدیریت برنام ه نویسی چند ریسمانی فراهم م ی کند. احتمالاً اکثر برنام ه نویس ها هرگز نیازی به

پشتیبانی ریسمان بندی را در داخل کلا س ها بصورت انتزاعی درم ی آورد تا CLR مدیریت صریح ریسما ن ها نداشتند، چون

چندریسمانی را ساده سازد.

بخش اول این فصل نحو ه ی ایجاد، مدیریت و از بین برد ن 1 ریسمان ها را نشان می دهد. حتی اگر ریسمان های خود را صریحاً

ایجاد نکرده باشند، م ی خواهید مطمئن باشید که کد شما م ی تواند چند ریسمانی را اداره کن د . اگر در یک محیط

چندریسمانی 2 اجرا شود یا در قطعاتی که ممکن است توسط برنام ه نویسان دیگر در برنام ه های چندریسمانی استفاده شوند،

باید این ایده را در نظر گرف ت . این عمل برای توسع ه دهندگان سروی س های وب و کنترل از راه دور مهم اس ت . اگرچه

برنامه های تحت وب اکثر ویژگ ی های برنامه های رومیزی را دارند، آنها روی یک سرور اجرا م ی شوند و بایستی اید ه های

بهره وری و چندریسمانی برای آنها در نظر گرفته شود.

بخش دوم فصل روی همگا م سازی تمرکز دار د . زمانی که یک منبع محدود (مانند یک اتصال پایگاه داد ه ) دارید، ممکن است

محدود کردن دسترسی به آن منبع در یک لحظه توسط یک ریسمان لازم باشد.

یک شباهت کلاسیک، توالت هواپیما اس ت . می خواهید دسترسی به توال ت را در هر لحظه فقط برای یک نفر مجاز داری د . این

عمل با یک قفل روی در انجام م ی شود. زمانی که مسافر م ی خواهد توالت را استفاده کند، آنها سعی م ی کنند در را کنترل

1 Kill

2 MultiThread

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

440

کنند. اگر در قفل شده باشد، آنها دنبال کار دیگری م ی روند یا در یک صف منتظر م یمانند. زمانی که منبع آزاد می شود، یک

فرد دیگر از صف خارج شده و منبع را م یگیرد و آن را قفل م یکند.

گاهی اوقات، ممکن است ریسما ن های متعددی بخواهند به یک منبع در برنام ه ی شما دسترسی داشته باشن د (همچون یک

فایل). شاید مهم باشد مطمئن شوید که در هر لحظه فقط یک ریسمان به منبع دسترسی د ارد و منبع خود را قفل کرده و بعد

از دستیابی یک ریسمان به آن، منبع را آزاد کنی د . قفل های برنامه نویسی با توجه به توزیع منابع م ی توانند بسیار پیچیده

باشند.

-1-24 ریسمان ها

در صورتی که یک برنامه بخواهد در یک لحظه دو کار بطور همزمان انجام دهد، ریسما ن ها استفاده می شوند . برای مثال،

را با 10 بلیون رقم اعشار حساب کنی د . بعد از شروع این محاسبه، تا زمانی که آن کار م ی کند، برنامه هیچ Pi می خواهید

خروجی به واسط کاربر نم ی نویسد. چون این محاسبه چند میلیون سال طول م ی کشد. حال ممکن است بخواهید مقدار جدید

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

لحظه بتواند آن را متوقف ساز د . در صورتی که برنامه شما اجازه دارد کلیک روی دکمه توقف را اداره کند، شما ریسمان

اجرایی دیگری لازم دارید.

کاربرد معمول دیگر از ریسما ن بندی، در صورتی است که باید منتظر یک رویداد بمانی د : همچون ورودی کاربر، خواندن از فایل

یا دریافت داده از شبک ه . در حالیکه پروس ه ی شما منتظر است، آزاد کردن پردازنده برای شروع اجرای عمل دیگر، یک ایده

خوب است و آن اجرای برنامه شما را سریع تر می سازد.

از طرف دیگر، به بعضی از شرایط توجه کنی د . ریسمان بندی می تواند سرعت اجرای برنامه را پایین بیاور د . فرض کنید

سری فیبوناجی را نیز بدست آوری د . اگر یک چند پردازنده داشته باشید، حتی اگر هر کدام ،Pi می خواهید علاوه بر محاسبه

ریسمان خود را داشته باشند، سرعت بالاس ت . ولی اگر از یک پ ردازنده استفاده کنیم، محاسب ه ی این مقادیر در چند ریسمان،

قطعاً با سرعت کمتری انجام خواهد شد. چون عمل سوییچ مابین ریسمان ها انجام م یشود که یک سربار است.

-1-1-24 شروع ریسما نها

یک آرگومان از نوع Thread است. سازندهی Thread ساده ترین راه ایجاد یک ریسمان، ایجاد یک نمونه جدید از کلاس

را برای این منظور تهیه کرده است، که به متد مورد نظر شما اشاره ThreadStart کلاس نماینده CLR . نماینده می گیرد

می کند. این کلاس به شما اجازه م ی دهد، یک ریسمان ایجاد کنید و به آن بگویید "زمانی که شروع شدی، این متد را اجرا

بصورت زیر است. ThreadStart کن". اعلان نمایندهی

public delegate void ThreadStart();

void همانطور که می بینید متدی که شما به این نماینده الحاق م ی کنید، نباید پارامتری داشته باشد و مقدا ر

برمیگرداند.پس یک ریسمان جدید را بصورت زیر ایجاد میکنید.

Thread myThread = new Thread( new ThreadStart(myFunc) );

برای مثال: می خواهیم دو ریسمان ایجاد کنیم که یکی از صفر به بالا می شمارد و دیگری از 1000 به پایین می شمارد.

public void Incrementer()

{

for (int i =0;i<1000;i++)

{

Console.WriteLine("Incrementer: {0}", i);

}

فصل بیست و چهارم ریسما نها و همگام سازی

441

}

public void Decrementer()

{

for (int i = 1000;i>=0;i--)

{

Console.WriteLine("Decrementer: {0}", i);

}

}

مقداردهی ThreadStart برای اجرای این متدها در ریسمانها، دو ریسمان جدید ایجاد کنید و هرکدام را با یک نمایند ه ی

اولیه کنید. این نمایندهها در ابتدا با توابع عضو مربوطه مقداردهی اولیه م یشوند.

Thread t1 = new Thread( new ThreadStart(Incrementer) );

Thread t2 = new Thread( new ThreadStart(Decrementer) );

Thread مربوطه به شی Start با ایجاد نمون ه هایی از ریسما ن ها، اجرای آنها شروع نم ی شود. برای انجام این کار، باید متد

فراخوانی شود.

t1.Start();

t2.Start();

توجه: اگر کار دیگری انجام ندهد، بعد از اینکه تابع تمام شود، ریسمانها متوقف م یگردند.

System.Threading Using 1 یک برنامهی کامل و خروجی آن را نشان میدهد. لازم است با یک دستور - مثال 24

1 سوئیچ t 2 و t با خبر سازید. به خروجی توجه کنید، م ی توانید ببینید که پردازنده ما بین Thread کامپایلر را از وجود کلاس

می کند.

1- مثال 24

#region Using directives

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

#endregion

namespace UsingThreads

{

class Tester

{

static void Main( )

{

// make an instance of this class

Tester t = new Tester( );

Console.WriteLine( "Hello" );

// run outside static Main

t.DoTest( );

}

public void DoTest( )

{

// create a thread for the Incrementer

// pass in a ThreadStart delegate

// with the address of Incrementer

Thread t1 =

new Thread(

new ThreadStart( Incrementer ) );

// create a thread for the Decrementer

// pass in a ThreadStart delegate

// with the address of Decrementer

Thread t2 =

new Thread(

new ThreadStart( Decrementer ) );

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

442

// start the threads

t1.Start( );

t2.Start( );

}

// demo function, counts up to 1K

public void Incrementer( )

{

for ( int i = 0; i < 1000; i++ )

{

System.Console.WriteLine(

"Incrementer: {0}", i );

} }

// demo function, counts down from 1k

public void Decrementer( )

{

for ( int i = 1000; i >= 0; i-- )

{

System.Console.WriteLine(

"Decrementer: {0}", i );

} } } }

Output (excerpt):

Incrementer: 102

Incrementer: 103

Incrementer: 104

Incrementer: 105

Incrementer: 106

Decrementer: 1000

Decrementer: 999

Decrementer: 998

Decrementer: 997

ابتدا پردازنده به اولین ریسمان اجازه م ی دهد تا شمردن 106 اجرا گردد. سپس ریسمان دوم مشارکت کرده و شمردن از 1000

به پایین را آغاز م ی کند. سپس به اولین ریسمان اجاز ه ی اجرا دا د ه می شود. مدت زمان واقعی اختصاص داده شده به هر

ریسمان بوسیله زما ن بند ریسمان اداره م ی شود و به فاکتورهای زیادی بستگی اس ت : همچون سرعت پردازنده، درخواست

پردازنده از طرف برنامه های دیگر.

-2-1-20 پیوندزدن ریسمان ها

زمانی که م ی خواهید به یک ریسمان بگویید تا اجر ای کامل ریسمان دیگر منتظر بماند، ریسمان اول را به ریسمان دوم پیوند

زنید، یعنی سر ریسمان اول را به ته ریسمان دوم پیوند زدید.

2) بنویسید. t) 1) به ریسمان 2 t) برای پیوند زدن ریسمان 1

t2.Join( );

2 منتظر خواهد مان د . برای مثال، t 1 مکث خواهد کرد و برای ت ک میل و خروج t ، 1 اجرا شود t اگر این دستور در یک متد در

از ریسمان بخواهد تا اتمام همه ریسما ن های دیگر منتظر بماند قبل از اینکه آن پیام ،()Main ممکن است در بدن ه ی متد

ایجاد کرد ه اید . MyThreads مندرج شده را بنویس د . در تکه کد بعدی، فرض کنید یک کلکسیون از ریسما ن ها بنام

کلکسیون را طی میکند و ریسمان جاری را به همه ریسما نهای کلکسیون پیوند میزند.

foreach (Thread myThread in myThreads)

فصل بیست و چهارم ریسما نها و همگام سازی

443

{

myThread.Join();

}

Console.WriteLine("All my threads are done.");

پیام آخری تا زمانیکه همه ریسما ن ها پایان نیافت ه اند، اجرا نخواهد ش د . در یک محیط تولید، ممکن است یک دنباله از

ریسمان ها را برای بنا کردن بعضی کارها شروع کنید و ادامه اجرای ریسمان اصلی را تا کامل شدن همه ریسما ن ها متوقف

سازید.

Sleep -3-1-20 بلوکه کردن ریسمان ها با

گاهی اوقات، م ی خواهید ریسمان تان را برای یک مدت کوتاهی معلق ساز ی د. برای مثال ممکن است جهت تست زمان

سیستم، ساعت را به مدت یک ثانیه معلق سازی د . این به شما اجازه م ی دهد، زمان جدید را در حدود یک ثانیه بدون اختصاص

هزاران میلیون سیکل ماشین نمایش دهید.

می شود . یک نسخه از آن overload یک متد ایستای عمومی برای این منظور پیشنهاد م یکند. این متد Thread کلاس

را به عنوان ورودی م ی گیرد. هر کدام مقدار زمان معلق کردن timeSpan و نسخه ی دیگر آن یک ش ی int یک مقدار

ریسمان را با واحد میلیونم ثانیه نمایش م یدهند. (مقدار صحیح 2000 یعنی 2 ثانیه)

میلی ثانیه اس ت . ()Sleep می توانند تیک ها ( 100 نانو ث انیه) را اندازه گیری کنند، واحد داد ه ها در متد timespan اگرچه اشیاء

را احضار کنید که ()Thread.Sleep برای اینکه ریسما ن تان را به یک ثانیه خواب وادار کنید، م ی توانید متد ایستای

ریسمان احضار شده را معلق م یسازد.

Thread.Sleep(1000);

را با مقدار صفر فراخوانی م ی کنند. بدین منظور که به زما ن بند ریسمان القاء کنند، نوبت اجرا را به Sleep گاهی اوقات، متد

ریسمانی دیگر بدهد، حتی اگر زمانبند مقدار بیشتری زمان به ریسمان شما داده باشد.

تغییر دهید، خروجی تغییر Writeln 1) بعد از هر دستور Thread.Sleep( 1 را با اضافه کردن یک دستور - اگر مثال 24

می یابد.

for (int i =0;i<1000;i++)

{

Console.WriteLine( "Incrementer: {0}", i);

Thread.Sleep(1);

}

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

تغییر را منعکس می کند.

Incrementer: 0

Incrementer: 1

Decrementer: 1000

Incrementer: 2

Decrementer: 999

Incrementer: 3

Decrementer: 998

Incrementer: 4

Decrementer: 997

Incrementer: 5

Decrementer: 996

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

444

Incrementer: 6

Decrementer: 995

-4-1-24 از بینبردن ریسمان ها

معمولاً، ریس مان ها بعد از اجرای دورهی خود از بین می روند. می توانید از یک ریسمان بخواهید خود را از بین ببر د .

است که ریسمان بصورت دور ه ای آن را بررسی م ی کند. زمانی که حالت KeepAlive واضح ترین روش تنظیم فلگ بولین

فلگ تغییر یابد، ریسمان می تواند خود را متوقف سازد.

است، که از ریسمان م ی خواهد خود را از بین ببر د . نهایتاً در لاعلاجی و اگر ()Thread.Interupt روش د یگر فراخوانی

را فراخوانی کنی د . این عمل یک استثنای Thread. Abort برنامه بخواهد خود را متوقف سازد، ممکن است

رها می کند، که ریسمان م ی تواند تشخیص ده د. ریسمان با استثنای ThreadAbortException

بصورت یک سیگنال برخورد م ی کند تا فوراً ریسمان را از بین بر د . در هر حال، شما شبیه یک ThreadAbortException

سیاست خودکشی، ریسمان را نم یکشید.

ریسمان را از بین ببری د . ممکن است ،Cancel ممکن است بخواهید در واکنش به یک رویداد همچون کلیک کارب ر روی دکمه

2 باشد . در اداره کنند ه ی t 1 باشد و رویدادی که لغو م ی شود در ریسمان t در ریسمان Cancel اداره کننده ی رویداد دکمه

1 فراخوانی کنید. t را روی Abort رویدادتان می توانید

t1.Abort();

1 می تواند آنرا تشخیص دهد. t 1 رها می شود که t یک استثناء در متد جاری

ذخیره می شوند . قبل از شروع ریسما ن ها Thread 2 سه ریسمان ایجاد م ی شوند و در یک آرایه از اشیاء - در م ثال 24

قرار دهید )ریسمان های زمینه دقیقاً شبیه ریسما ن های پیش زمینه اجرا م ی شوند True آنها را IsBackground خصوصیات

به استثناء اینکه آنها نم ی توانند مانع خاتمه یاف ت ن یک پردازش شون د (. هر ریسمان نامگذاری شده و شروع م ی شود )همچون

2. یک پیام برای نشان دادن شروع ریسمان نمایش داده م ی شود و سپس ریسمان اصلی قبل از شروع Thread) 1 و Thread

ریسمان بعدی 50 میلی ثانیه خواب می رود.

کنار گذاشته م ی شود. سپس ،()Abort بعد از شروع سه ریسمان، 50 میلی ثانیه دیگر م ی گذرد و اولین ریسمان با فراخوانی

ریسمان اصلی هر سه ریسمان در حال اجرا را بهم پیوند م ی زند. اثر عمل این است که تا زمانیکه همه ریسما ن های دیگر کامل

All My Thread Are نشده ____________اند، ریسمان اصلی ادامه نخواهد یافت. زمانی که آنها کامل شوند، ریسمان اصلی یک پی ا م

2 نمایش داده می شود. - را چاپ می کند. کد منبع کامل در مثال 24 Done

2- مثال 24

#region Using directives

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

#endregion

namespace InterruptingThreads

{

class Tester

{

static void Main( )

{

// make an instance of this class

Tester t = new Tester( );

// run outside static Main

t.DoTest( );

فصل بیست و چهارم ریسما نها و همگام سازی

445

}

public void DoTest( )

{

// create an array of unnamed threads

Thread[] myThreads =

{

new Thread( new ThreadStart(Decrementer) ),

new Thread( new ThreadStart(Incrementer) ),

new Thread( new ThreadStart(Decrementer) ),

new Thread( new ThreadStart(Incrementer) )

};

// start each thread

int ctr = 1;

foreach (Thread myThread in myThreads)

{

myThread.IsBackground = true;

myThread.Start( );

myThread.Name = "Thread" + ctr.ToString( );

ctr++;

Console.WriteLine("Started thread {0}",

myThread.Name);

Thread.Sleep(50);

}

// ask the first thread to stop

myThreads[0].Interrupt( );

// tell the second thread to abort immediately

myThreads[1].Abort( );

// wait for all threads to end before continuing

foreach (Thread myThread in myThreads)

{

myThread.Join( );

}

// after all threads end, print a message

Console.WriteLine("All my threads are done.");

}

// demo function, counts down from 100

public void Decrementer( )

{

try

{

for (int i = 100; i >= 0; i--)

{

Console.WriteLine(

"Thread {0}. Decrementer: {1}",

Thread.CurrentThread.Name,

i);

Thread.Sleep(1);

}

}

catch (ThreadAbortException)

{

Console.WriteLine(

"Thread {0} aborted! Cleaning up...",

Thread.CurrentThread.Name);

}

catch (System.Exception e)

{

Console.

WriteLine("Thread has been interrupted ");

}

finally

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

446

{

Console.WriteLine(

"Thread {0} Exiting. ",

Thread.CurrentThread.Name);

} }

// demo function, counts up to 100

public void Incrementer( )

{

try

{

for (int i = 0; i < 100; i++)

{

Console.WriteLine(

"Thread {0}. Incrementer: {1}",

Thread.CurrentThread.Name,

i);

Thread.Sleep(1);

}

}

catch (ThreadAbortException)

{

Console.WriteLine(

"Thread {0} aborted!",

Thread.CurrentThread.Name);

}

catch (System.Exception e)

{

Console.

WriteLine("Thread has been interrupted");

}

finally

{

Console.WriteLine(

"Thread {0} Exiting. ",

Thread.CurrentThread.Name);

}}}}

Output (excerpt):

Started thread Thread1

Thread Thread1. Decrementer: 100

Thread Thread1. Decrementer: 99

Started thread Thread2

Thread Thread2. Incrementer: 0

Thread Thread1. Decrementer: 98

Started thread Thread3

Thread Thread3. Decrementer: 100

Thread Thread1. Decrementer: 97

Thread Thread2. Incrementer: 1

Started thread Thread4

Thread Thread4. Incrementer: 0

Thread Thread2 aborted!

Thread Thread3. Decrementer: 99

Thread Thread2 Exiting.

Thread has been interrupted

Thread Thread3. Decrementer: 98

Thread Thread4. Incrementer: 1

فصل بیست و چهارم ریسما نها و همگام سازی

447

Thread Thread1 Exiting.

Thread Thread3. Decrementer: 97

Thread Thread3. Decrementer: 1

Thread Thread4. Incrementer: 98

Thread Thread3. Decrementer: 0

Thread Thread4. Incrementer: 99

Thread Thread3 Exiting.

Thread Thread4 Exiting.

All my threads are done.

دیدید که ابتدا ریسمان اول شروع م ی شود و از 100 تا 99 می شمارد . سپس ریسمان دوم شروع م ی شود تا زمانیکه

2 گزارش thread ، ریسمان های سوم و چهارم آغاز شوند، این دو ریسمان با هم اجرا م ی شوند. بعد از یک مدت زمان کو تاه

1 گزارش thread می دهد که کنار گذاشته شده است و سپس گزارش م ی دهد در حال خروج اس ت . بعد از زمان کمتری

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

نیست.

دو ریسمان باقیمانده تا زمان انجام کار خود، ادامه م ی یابند. آنها بطور طبیعی خارج می گردند و ریسمان اصلی که به همه

ریسمانها پیوند خورده بود، با چاپ پیام خروج خود ادامه می یابد.

-2-24 همگام سازی

گاهی اوقات، ممکن است بخواهید دسترسی به یک منبع همچون خصوصیات و متدهای یک ش ی را کن ت رل کنید، تا اینکه در

هر لحظه فقط یک متد بتواند آن منبع را استفاده یا تغییر ده د . شی شما شبیه توالت هواپیما است و ریسما ن های مرتبط

شبیه افراد منتظر در صف هستن د . همگامسازی با یک قفل روی ش ی فراهم م ی شود. این عمل به توسع ه دهنده کمک م ی کند

تا زمانیکه کار ریسمان اول روی آن شی تمام نشده است، ش ی دیگری سرزده به آن دسترسی نداشته باشد.

در ابتدا .Moniter و کلاس ،#C در lock دستور ،InterLock این بخش سه مکانیزم همگا م سازی را بررسی م ی کند: کلاس

.(Counter شما به ایجاد یک منبع اشتراکی نیاز داری د ( اغلب یک فایل یا چاپگر یا در حالت س اده یک متغیر صحیح بنام

را در هر دو ریسمان افزایش خواهید داد. Counter شما مقدار

برای شروع، متغیر عضو را اعلان کرده و آنرا با صفر مقداردهی کنید.

int counter = 0;

تغییر دهید. Counter را برای افزایش متغیر عضو Incrementer متد

public void Incrementer()

{

try

{

while (counter < 1000)

{

int temp = counter;

temp++; // increment

// simulate some work in this method

Thread.Sleep(1);

// assign the Incremented value

// to the counter variable

// and display the results

counter = temp;

Console.WriteLine(

"Thread {0}. Incrementer: {1}",

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

448

Thread.CurrentThread.Name,

counter);

}}

هدف ما در اینجا شبی ه سازی کاری است که ممکن است با یک منبع کنترل شده انجام م ی شود. عیناً همانطور که ممکن است

یک فایل را باز کنید، محتوای آن را تغییر داده و آن را ببندید.

را به یک متغیر موقتی خوانده و متغیر موقتی را افزایش دهید و برای شبی ه سازی این کار یک میلی Counter در اینجا م قدار

برگردانید. Counter ثانیه آنرا بخوابانید و سپس مقدار افزایش یافته را به

0) را می خواند و آن را به یک متغیر موقت انتساب م یدهد. سپس مقدار Counter ( مشکل این است که ریسمان اول مقدار

0) را Counter ( متغیر موقت را افزایش می دهد. زمانی که آن ریسمان کارش را انجام م ی دهد، ریسمان دوم مقدار

می خواند و مقدار آن را به یک متغیر موقت انتساب م ی دهد. ریسمان اول کار خود را خاتمه داده و مقدار متغیر موقت ( 1) را به

انتساب داده و آنرا نمایش م ی دهد. ریسمان دوم نیز همان کار را انجام م ی دهد. 1و 1 چاپ م ی شود در دور بعدی، Counter

همان چیز اتفاق م ی افتد. به جای این که دو ریسمان مقادیر 1و 2و 3و 4و . . . را بشمارند، مقادیر 1و 2و 3و 3و 4و 4و . . . را چاپ

می کند.

3 خروجی و کد منبع کامل را برای این مثال نشان می دهد. - مثال 24

3- مثال 24

#region Using directives

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

#endregion

namespace SharedResource

{

class Tester

{

private int counter = 0;

static void Main( )

{

// make an instance of this class

Tester t = new Tester( );

// run outside static Main

t.DoTest( );

}

public void DoTest( )

{

Thread t1 = new Thread( new ThreadStart( Incrementer ) );

t1.IsBackground = true;

t1.Name = "ThreadOne";

t1.Start( );

Console.WriteLine( "Started thread {0}",

t1.Name );

Thread t2 = new Thread( new ThreadStart( Incrementer ) );

t2.IsBackground = true;

t2.Name = "ThreadTwo";

t2.Start( );

Console.WriteLine( "Started thread {0}",

t2.Name );

t1.Join( );

فصل بیست و چهارم ریسما نها و همگام سازی

449

t2.Join( );

// after all threads end, print a message

Console.WriteLine( "All my threads are done." );

}

// demo function, counts up to 1K

public void Incrementer( )

{

try

{

while ( counter < 1000 )

{

int temp = counter;

temp++; // increment

// simulate some work in this method

Thread.Sleep( 1 );

// assign the decremented value

// and display the results

counter = temp;

Console.WriteLine(

"Thread {0}. Incrementer: {1}",

Thread.CurrentThread.Name,

counter );

}}

catch ( ThreadInterruptedException )

{

Console.WriteLine(

"Thread {0} interrupted! Cleaning up...",

Thread.CurrentThread.Name );

}

finally

{

Console.WriteLine(

"Thread {0} Exiting. ",

Thread.CurrentThread.Name );

}}}}

Output:

Started thread ThreadOne

Started thread ThreadTwo

Thread ThreadOne. Incrementer: 1

Thread ThreadOne. Incrementer: 2

Thread ThreadOne. Incrementer: 3

Thread ThreadTwo. Incrementer: 3

Thread ThreadTwo. Incrementer: 4

Thread ThreadOne. Incrementer: 4

Thread ThreadTwo. Incrementer: 5

Thread ThreadOne. Incrementer: 5

Thread ThreadTwo. Incrementer: 6

Thread ThreadOne. Incrementer: 6

Interlocked -1-2-20 کاربرد

یک تعداد مکانیزم همگا م سازی فراهم م ی کند. آنها شامل ابزار همگا م سازی عمومی همچون بخ ش های بحرانی (با نام CLR

هستند. هر کدام در ادامهی این بخش بحث می شوند. Monitor و کلاس (NET قفل ها در

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

450

افزایش و کاهش یک مقدار، یک الگوی برنام ه نویسی عمومی است و آن اغلب به حفاظت از طریق همگا م سازی نیاز دارد، که

فقط دو متد با نا م های Interlocked . فقط برای این منظور پیشنهاد م ی کند Interlocked یک کلاس خاص بنام CLR

دارد که علاوه بر افزایش و کاهش یک مقدار، کنترل همگا م سازی را نیر در نظر م ی گیرند. متد Decrement و Increment

3 را بصورت زیر تغییر دهید. - مثال 24 Incrementer

public void Incrementer( )

{

try

{

while (counter < 1000)

{

int temp = Interlocked.Increment(ref counter);

// simulate some work in this method

Thread.Sleep(0);

// display the incremented value

Console.WriteLine(

"Thread {0}. Incrementer: {1}",

Thread.CurrentThread.Name,

temp);

}}}

یک پارامتر ()InterLocked.Increment و مابقی برنامه عیناً مانند مثال قبلی اس ت . متد Finally و Catch بلوک های

را به ref بصورت مقداری ارسال م ی شوند، کلم ه ی کلیدی int را می پذیرد. چون مقادیر int واحد از نوع ارجاع به یک

همراه آن بکار برید.

را بگیرد. long یک نوع ، int م یشود و م یتواند به جای یک نوع Overload ، Increment() متد

همگام می شود و خروجی آن همان چیزی است که انتظار Counter زمانی که این تغییر انجام شود ، دسترسی به عضو

می رفت.

Output (excerpts):

Started thread ThreadOne

Started thread ThreadTwo

Thread ThreadOne. Incrementer: 1

Thread ThreadTwo. Incrementer: 2

Thread ThreadOne. Incrementer: 3

Thread ThreadTwo. Incrementer: 4

Thread ThreadOne. Incrementer: 5

Thread ThreadTwo. Incrementer: 6

Thread ThreadOne. Incrementer: 7

Thread ThreadTwo. Incrementer: 8

Thread ThreadOne. Incrementer: 9

Thread ThreadTwo. Incrementer: 10

Thread ThreadOne. Incrementer: 11

Thread ThreadTwo. Incrementer: 12

Thread ThreadOne. Incrementer: 13

Thread ThreadTwo. Incrementer: 14

Thread ThreadOne. Incrementer: 15

Thread ThreadTwo. Incrementer: 16

Thread ThreadOne. Incrementer: 17

Thread ThreadTwo. Incrementer: 18

Thread ThreadOne. Incrementer: 19

فصل بیست و چهارم ریسما نها و همگام سازی

451

Thread ThreadTwo. Incrementer: 20

-2-2-24 کاربرد قف لها

خوب است، در بعضی مواقع م ی خواهید InterLocked اگر بخواهید یک مقدار را افزایش یا کاهش دهید، اگرچه ش ی

دسترسی به منابع دیگر را نیز کنترل کنی د . چیزی که لازم است، یک مکا نیزم همگام سازی کلی م یباشد . این مکانیزم با

فراهم می شود. #C در lock ویژگی

یک بخش بحرانی از کد را علام ت گذاری می کند. همگامسازی روی ش ی برگزیده ی شما را با یک قفل فراهم م ی سازد . ،lock

درخواست یک قفل روی یک ش ی و اجرای یک دستور یا بلوکی از دستورات اس ت . قفل در انتهای بلوک ،lock گرامر کاربرد

دستورات آزاد می شود.

فراهم می کند. یک ارجاع به یک ش ی را به آن ارسال کرده و به lock پشتیبانی مستقیم قف ل ها را از طریق کلمه کلیدی #C

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

lock(expression) statement-block

بصورت زیر تغییر دهید. lock را با استفاده یک دستور Incrementer مثال: می توانید متد

public void Incrementer()

{

try

{

while (counter < 1000)

{

int temp;

lock (this)

{

temp = counter;

temp ++;

Thread.Sleep(1);

counter = temp;

}

// assign the decremented value

// and display the results

Console.WriteLine(

"Thread {0}. Incrementer: {1}",

Thread.CurrentThread.Name,

temp);

}}

و بقیه برنامه همانند مثال قبلی هستن د . خروجی این کد، مساوی خروجی تولید شده با Finally و Catch بلوک های

است. InterLocked استفاده از

-3-2-20 کاربرد مانیتورها

اشیاء استفاده شدة تاکنون، برای بیشتر نیازها کافی هستن د . برای کنترل پیشرفته روی منابع، ممکن است بخواهید یک

مانیتور بکار بری د . یک مانیتور به شما اجازه می دهد تصمیم بگیرید یک همگا م سازی کی وارد و کی خارج شود و به شما

اجازه می دهد برای آزادشدن فضای دیگری از کدتان منتظر بمانید.

را با ارسال شی موردنظر جهت قفل کردن، به ()Monitor.Enter زمانی که م ی خواهید همگام سازی را شروع کنید، متد

آن فراخوانی کنید.

Monitor.Enter(this);

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

452

اگر مانیتور در دسترس نباشد، فرض م ی شود شی محافظت شده بوسیله مانیتور در حال استفاده ا س ت، می توانید کار دیگری

را ()Wait انجام دهید . در حالی که منتظر هستید مانیتور در دسترس قرار گیرد، مجدداً سعی کنی د . می توانید صریحاً

را برای بیدار ()Pulse فراخوانی کنید، که ریسمان شما را تا زمانیکه مانیتور مشغول است معلق م ی سازد و توسع ه دهنده

در کنترل نظم و ترتیب ریسما نها کمک می کند. ()Wait . کردن ریسمان معلق فراخوانی می کند

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

شده است. Download چاپ در زمینه انجام شود. اما می خواهید مطمئن شوید قبل از شروع عمل چاپ حداقل 10 صفحه

ریسمان چاپ شما منتظر خواهد ماند تا زمانیکه ریسمان گرفتن فایل، خوانده شدن اندازة کافی از فایل را سیگنال ده د .

کنید، چون احتمال دارد فایل صدها صفحه باشد و نم ی خواهید تا پایا ن یافتن عمل Join نمی خواهید ریسمان گرفتن فایل را

شده است . متد Download منتظر بما نید. اما می خواهید مطمئن شوید، قبل از چاپ فایل حداقل 10 صفحه از آن Download

عین یک بلیط است. ()Wait

تا 10 به Incrementer . را به آن اضافه کنی د Decrementer را مجدداً بنویسید و متد Tester برای شبیه سازی این مورد

به پایین تا صفر م ی شمارد. شما کاهش شمارنده را شروع نم ی کنید، مگر اینکه مقدار Decrementer بالا می شمارد و متد

حداقل 5 باشد. counter

را بررسی کنید، اگر کمتر از 5 باشد، متد counter را روی مانیتور فراخوانی کنی د . سپس مقدار Enter ،Decrementer در

را روی مانیتور فراخوانی کنید. ()Wait

if (counter < 5)

{

Monitor.Wait(this);

}

سیگنال می دهد زمانی که مانیتور آزاد شود، مجددا برگرد د . اگر ریسمان CLR مانیتور را آزاد م ی کند. اما به ،()Wait فراخوانی

را فراخوانی کند، ریسما نهای منتظر یک شانس برای اجرای مجدد دریافت م یکنند. Pulse فعال

Monitor.Pulse(this);

سیگنال می دهد که تغییری در حالت رخ داده است، که ممکن است یک ریسمان منتظر را آزاد ساز د . CLR به ()Pulse متد

()Exit زمانی که یک ریسمان مربوط به مانیتور پایان م ی یابد، آن باید انتهای ناحی ه ی کد کنترل شده را با فراخوانی

علامتگذاری کند.

Monitor.Exit(this);

Monitor را با استفاده از counter 4 شبیه سازی را ادامه م ی دهد. یک دسترسی همگا م سازی شده بر یک متغیر - مثال 24

فراهم می سازد.

4- مثال 24

#region Using directives

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

#endregion

namespace UsingAMonitor

{

class Tester

{

private long counter = 0;

static void Main( )

{

// make an instance of this class

فصل بیست و چهارم ریسما نها و همگام سازی

453

Tester t = new Tester( );

// run outside static Main

t.DoTest( );

}

public void DoTest( )

{

// create an array of unnamed threads

Thread[] myThreads =

{

new Thread( new ThreadStart(Decrementer) ),

new Thread( new ThreadStart(Incrementer) )

};

// start each thread

int ctr = 1;

foreach ( Thread myThread in myThreads )

{

myThread.IsBackground = true;

myThread.Start( );

myThread.Name = "Thread" + ctr.ToString( );

ctr++;

Console.WriteLine( "Started thread {0}", myThread.Name );

Thread.Sleep( 50 );

}

// wait for all threads to end before continuing

foreach ( Thread myThread in myThreads )

{

myThread.Join( );

}

// after all threads end, print a message

Console.WriteLine( "All my threads are done." );

}

void Decrementer( )

{

try

{

// synchronize this area of code

Monitor.Enter( this );

// if counter is not yet 10

// then free the monitor to other waiting

// threads, but wait in line for your turn

if ( counter < 10 )

{

Console.WriteLine(

"[{0}] In Decrementer. Counter: {1}. Gotta Wait!",

Thread.CurrentThread.Name, counter );

Monitor.Wait( this );

}

while ( counter > 0 )

{

long temp = counter;

temp--;

Thread.Sleep( 1 );

counter = temp;

Console.WriteLine(

"[{0}] In Decrementer. Counter: {1}. ",

Thread.CurrentThread.Name, counter );

}}

finally

{

Monitor.Exit( this );

}}

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

454

void Incrementer( )

{

try

{

Monitor.Enter( this );

while ( counter < 10 )

{

long temp = counter;

temp++;

Thread.Sleep( 1 );

counter = temp;

Console.WriteLine(

"[{0}] In Incrementer. Counter: {1}",

Thread.CurrentThread.Name, counter );

}

// I'm done incrementing for now, let another

// thread have the Monitor

Monitor.Pulse( this );

}

finally

{

Console.WriteLine( "[{0}] Exiting...",

Thread.CurrentThread.Name );

Monitor.Exit( this );

}}}}

Output:

Started thread Thread1

[Thread1] In Decrementer. Counter: 0. Gotta Wait!

Started thread Thread2

[Thread2] In Incrementer. Counter: 1

[Thread2] In Incrementer. Counter: 2

[Thread2] In Incrementer. Counter: 3

[Thread2] In Incrementer. Counter: 4

[Thread2] In Incrementer. Counter: 5

[Thread2] In Incrementer. Counter: 6

[Thread2] In Incrementer. Counter: 7

[Thread2] In Incrementer. Counter: 8

[Thread2] In Incrementer. Counter: 9

[Thread2] In Incrementer. Counter: 10

[Thread2] Exiting...

[Thread1] In Decrementer. Counter: 9.

[Thread1] In Decrementer. Counter: 8.

[Thread1] In Decrementer. Counter: 7.

[Thread1] In Decrementer. Counter: 6.

[Thread1] In Decrementer. Counter: 5.

[Thread1] In Decrementer. Counter: 4.

[Thread1] In Decrementer. Counter: 3.

[Thread1] In Decrementer. Counter: 2.

[Thread1] In Decrementer. Counter: 1.

[Thread1] In Decrementer. Counter: 0.

All my threads are done.

فصل بیست و چهارم ریسما نها و همگام سازی

455

1 آغاز شده و سپس Decrementer) Thead) شروع می شود. در خروجی م یبینید که Decrementer در این مثال ابتدا

1 کارش را آغاز Thread ، 1 را بیدار م ی کند Thread ،2Thread 2 آغاز می شود. فقط زمانی که Thread منتظر می ماند. سپس

می کند.

1 هرگز Thread توضیحات اضافه کنی د . در می یابید که ()Pulse سعی کنید آزمایشاتی روی این کد انجام دهی د . ابتدا به م ت د

هیچ سیگنالی به ریسما نهای منتظر وجود ندارد. ،()Pulse ادامه نمی یابد. بدون

را مجدداً طوری بنویسید که بعد از هر عمل افزایش مانیتور را بیدار کرده و از آن Incrementer ، به عنوان آزمایش دوم

خارج شود.

void Incrementer()

{

try

{

while (counter < 10)

{

Monitor.Enter(this);

long temp = counter;

temp++;

Thread.Sleep(1);

counter = temp;

Console.WriteLine(

"[{0}] In Incrementer. Counter: {1}",

Thread.CurrentThread.Name, counter);

Monitor.Pulse(this);

Monitor.Exit(this);

}

بصورت زیر مجدداً بنویسید. While به یک دستور if را با تغییر دستور Decrementer متد

//if (counter < 10)

while (counter < 5)

Counter را بیدار کن د . زمانی که مقدار Decrementer 2 بعد از هر عمل افزایش، متد Thread این تغییرات باعث م ی شوند

بطور کامل Decrementer ، از 5 بالا رود Counter باید منتظر بماند. زمانی که مقدار Decrementer ، کوچکتر از 5 باشد

می تواند مجدداً اجرا شو د . خروجی بصورت زیر نمایش داده Incrementer اجرا می شود. زمانی که آن اجرا شد، ریسمان

می شود.

[Thread2] In Incrementer. Counter: 2

[Thread1] In Decrementer. Counter: 2. Gotta Wait!

[Thread2] In Incrementer. Counter: 3

[Thread1] In Decrementer. Counter: 3. Gotta Wait!

[Thread2] In Incrementer. Counter: 4

[Thread1] In Decrementer. Counter: 4. Gotta Wait!

[Thread2] In Incrementer. Counter: 5

[Thread1] In Decrementer. Counter: 4.

[Thread1] In Decrementer. Counter: 3.

[Thread1] In Decrementer. Counter: 2.

[Thread1] In Decrementer. Counter: 1.

[Thread1] In Decrementer. Counter: 0.

[Thread2] In Incrementer. Counter: 1

[Thread2] In Incrementer. Counter: 2

[Thread2] In Incrementer. Counter: 3

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

456

[Thread2] In Incrementer. Counter: 4

[Thread2] In Incrementer. Counter: 5

[Thread2] In Incrementer. Counter: 6

[Thread2] In Incrementer. Counter: 7

[Thread2] In Incrementer. Counter: 8

[Thread2] In Incrementer. Counter: 9

[Thread2] In Incrementer. Counter: 10

توجه: در هنگام برنام ه نویسی برای برنام ه های پیچیده بایستی مسائل ب ن بست را در هنگام کاربرد قف ل ها در نظر گرف ت . اگر

باشند، برنامه در بن بست قرار میگیرد. Wait همهی ریسمانها در حالت

-3-20 خلاصه

ریسمانها مسئول چند وظیف هگی یک برنامهی کاربردی واحد هستند.

ریسمان بندی می تواند سرعت اجرای برنامه را پایین بیاورد.

است. Thread • ساده ترین راه ایجاد یک ریسمان، ایجاد یک نمونه جدید از کلاس

زمانی که م ی خواهید به یک ریسمان بگویید تا اجرای کامل ر یسمان دیگر منتظر بماند، ریسمان اول را به ریسمان

دوم پیوند زنید.

را احضار کنید که ریسمان احضار شده را معلق م یسازد. ()Thread.Sleep • می توانید متد ایستای

همگامسازی با یک قفل روی ش ی فراهم می شود.

افزایش و کاهش یک مقدار، یک الگوی برنام ه نویسی عمومی است و آن اغلب به حفاظت از طریق همگا م سازی نیاز

فقط برای این منظور پیشنهاد می کند. Interlocked یک کلاس خاص بنام CLR دارد، که

یک بخش بحرانی از کد را علامتگذاری می کند. ،lock •

یک مانیتور به شما اجازه می دهد تصمیم بگیرید یک همگامسازی کی وارد و کی خارج شود.

مانیتور را آزاد م یکند. ،()Wait • فراخوانی

 

Mohsen_mahyar@yahoo.com - C# برنامه نویسی

 

   + MOHSEN GHASEMI - ۸:۱۱ ‎ب.ظ ; ۱۳۸٩/٤/۳۱