PDA

View Full Version : آموزش: آشنایی با کلاس ConcurrentStack < T >



tooraj_azizi_1035
سه شنبه 13 تیر 1391, 02:01 صبح
سلام


یک مشکل رایج که کاربران در هنگام نوشتن برنامه های موازی با آن درگیر هستند فقدان وجود Thread-Safety در کلاس های کلکسیون .NET Framework است. کاربران اغلب نیاز دارند مکانیزم هماهنگ کنندگی خاص خود را برای دستیابی به هدف خواندن/نوشتن مطمئن داده ها در کلکسیون مشترک پیاده کنند.
یک راه حل منسوخ شده استفاده از کلکسیون های Synchronized که در .NET Framework نسخه 1.0 منتشر شده بود، یا استفاده از مکانیزم SyncRoot که از طریق اینترفیس ICollection در معرض استفاده قرار داده شده بود.
نه تنها هر دوی این دو روش برای استفاده توصیه نمی شوند، به دلایل متعدد، که کارآیی کمتر-از-حد- ایده آل را هم شامل می شود، بلکه با استفاده از آنها Developer ها در شرایط رقابت بسیار قرار می گیرند.
به خاطر این دلایل کلکسیون های جنریک در .NET Framework نسخه 2 منتشر شدند اساساً هیچگونه مکانیزم هماهنگی را پیشنهاد نکردند و Developer ها را مجبور ساختند تا خودشان عمل هماهنگ سازی را دستی انجام دهند.
Parallel Extensions در .NET Framework کمک می کند تا این شکاف را با انتشار کلکسیون های جنریک جدید که از مشکل مشابه که در نسخه قدیمی یعنی Synchronized وجود داشت رنج می برد، پر کند. ConcurrentStack<T> یکی از آنهاست.
در حال حاضر ConcurrentStack<T> به عنوان یک لیست پیوندی LIFO پیاده سازی شده و از یک تعویض مقایسه ای Interlocked برای اعمال Push و Pop استفاده می کند. با این وجود به شما عنوان یک کاربر توصیه نمی کنیم که روی جزئیات پیاده سازی داخلی تکیه کنید.

متدهای اصلی و پر استفاده یک ساختمان داده Stack معمولاً Push، Popو Peek هستند. نگاهی گذرا بر ConcurrentStack<T> نشان خواهد داد که Push وجود دارد، اما Popو Peek وجود ندارند: در عوض TryPop و TryPeek وجود دارند. این امر کاملاً عمدی است.


یکی از الگوهای بسیار رایج در هنگام استفاده از Stack<T> چک کردن خاصیت Count پشته است و اگر بزرگتر از صفر باشد یک عنصر از آن Pop کن و آن آیتم را استفاده کن، به طور مثال:






T item;
if (s.Count > 0)
{
item = s.Pop();
UseData(item);
}






اما در دنیایی که پشته توسط چندین Thread همزمان مورد دستیابی قرار میگیرد، حتی اگر متدهای Count و Pop به شکل Thread-Safe می بودند، ما همچنان گرفتار این مسئله می شدیم که پشته بین یک چک کردن موفق برای خالی-نبودن پشته و تلاش برای انجام یک عمل Pop، می تواند خالی شود.
ConcurrentStack<T> در API هایی که ارائه می کند این مسئله را به حساب آورده. برای تلاش جهت برداشتن مطمئن یک عنصر از پشته، ما میتوانیم این کد را به جای آن بنویسیم.




T item;
if (s.TryPop(out item))
{
UseData(item);
}



به این روش استفاده از ConcurrentStack<T> در یک سناریوی Producer/Consumer می تواند به شکل زیر پیاده شود:


//initialize an empty concurrent stack
ConcurrentStack<string> m_myConcurrentStack = new ConcurrentStack<string>();
//every producer will produce this number of elements
const int COUNT = 10;
public void ConsumeDataFromStack()
{
//create the producers
for (int i = 0; i < COUNT; i++)
{
Thread currentProducer = new Thread(new ThreadStart(delegate()
{
for(int currentIndex = COUNT; currentIndex > 0; currentIndex--)
{
m_myConcurrentStack.Push(
Thread.CurrentThread.ManagedThreadId.ToString() + "_" +
currentIndex.ToString());
}
}));
currentProducer.Start();
}
//allow the worker threads to start
Thread.Sleep(500);
//consume data
string currentData = "";
while (m_myConcurrentStack.TryPop(out currentData))
{
//ConsumeData(currentData);
}
}




اجرای PLINQ روی یک پشته موازی
ConcurrentStack<T> به عنوان یک IEnumerable<T> می تواند به عنوان منبع داده ها نیز در کوئری های PLINQ نیز استفاده شود.
در زیر نمونه ای از چنین استفاده وجود دارد.
volatile bool m_producersEnded = false;
const int COUNT = 10;
public void PLinqOverConcurrentStack()
{
//create the producers
Thread[] producers = new Thread[COUNT];
for (int i = 0; i < COUNT; i++)
{
Thread currentProducer = new Thread(new ThreadStart(delegate()
{
for(int currentIndex = COUNT; currentIndex > 0; currentIndex--)
{
m_myConcurrentStack.Push(currentIndex.ToString());
}
}));
producers[i] = currentProducer;
}
Thread PLINQConsumer = new Thread(new ThreadStart(delegate()
{
while (!m_producersEnded)
{
var currentValues = from data in m_myConcurrentStack
where data.Contains("9") select data;
foreach (string currentData in currentValues)
{
//consume data
}
}
}));
//start the consumer
pLinqConsumer.Start();
//start the producers
foreach (Thread producer in producers)
{
producer.Start();
}
//join the producers and the consumer
foreach (Thread producer in producers)
{
producer.Join();
}
m_producersEnded = true;
pLinqConsumer.Join();
}

Saeed_m_Farid
جمعه 16 تیر 1391, 17:57 عصر
سلام
بنده همانطورکه مستحضرید این مقاله شما رو خوندم و نکاتی که بنظرم میومد رو ذکر کردم ولی فکر می کنم وقت ترتیب اثر دادن نداشتید؛ بنابراین تصمیم گرفتم نسخه تصحیح شده رو اینجا بذارم تا برای سایر دوستان هم اگه از مقاله خوششون اومده باشه، بیشتر از اینی که هست، مفید واقع بشه:

_________________________________________
منبع: < Introducing ConcurrentStack < T (http://blogs.msdn.com/b/pfxteam/archive/2008/06/18/8614596.aspx)

+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=

آشنایی با کلاس <ConcurrentStack <T (http://msdn.microsoft.com/en-us/library/dd267331.aspx)

(http://msdn.microsoft.com/en-us/library/dd267331.aspx) سلام

یک مشکل رایج که کاربران در هنگام نوشتن برنامه های موازی با آن درگیر هستند عدم وجود Thread-Safety در کلاس‌های مجموعه (Collection) در NET Framework. است. کاربران اغلب نیاز دارند مکانیزم هماهنگ‌کنندگی خاص خود را برای دستیابی به هدف خواندن/نوشتن مطمئن داده ها در کلکسیون مشترک پیاده کنند.
یک راه حل منسوخ شده استفاده از کلکسیون های Synchronized که در NET Framework. نسخه 1.0 منتشر شده بود، یا استفاده از مکانیزم SyncRoot (http://msdn.microsoft.com/en-us/library/system.collections.icollection.syncroot.aspx) که از طریق اینترفیس ICollection (http://msdn.microsoft.com/en-us/library/92t2ye13.aspx) در معرض استفاده قرار داده شده بود.
علاوه بر اینکه به دلایل متعدد، هیچ‌کدام این دو روش برای استفاده توصیه نمی شوند (که کارآیی کمتر ازحد ایده آل را نیز شامل می شود)؛ علیرغم این با استفاده از آنها، توسعه‌دهندگان خود در شرایط رقابتی سختی قرار می‌گیرند (جهت مشاهده مشکلات چنین طراحی اینجا (http://blogs.msdn.com/b/bclteam/archive/2005/03/15/396399.aspx) را ببینید).
به خاطر این دلایل مجموعه‌های جنریک در NET Framework. نسخه 2 منتشر شدند اساساً هیچگونه مکانیزم هماهنگی را پیشنهاد نکردند و توسعه‌دهندگان را مجبور ساختند تا خودشان عمل هماهنگ سازی را دستی انجام دهند.
Parallel Extensions (http://www.codeproject.com/Articles/72235/Parallel-Extensions-to-the-NET-4-0-Runtime) در NET Framework. کمک می کند تا این شکاف را با انتشار مجموعه‌های جنریک جدید که از مشکل مشابه که در نسخه قدیمی یعنی Synchronized وجود داشت رنج می برد، پر کند. <ConcurrentStack <T یکی از آنهاست.
در حال حاضر <ConcurrentStack <T به عنوان یک لیست پیوندی LIFO پیاده سازی شده و از یک تعویض مقایسه ای Interlocked برای اعمال Push و Pop استفاده می کند. با این وجود به شما عنوان یک کاربر توصیه نمی کنیم که روی جزئیات پیاده سازی داخلی تکیه کنید.

متدهای اصلی و پر استفاده یک ساختمان داده Stack معمولاً Push ،Pop و Peek هستند. نگاهی گذرا بر <ConcurrentStack <T نشان خواهد داد که Push وجود دارد، اما Pop و Peek وجود ندارند: در عوض TryPop و TryPeek وجود دارند. این امر کاملاً عمدی است.
یکی از الگوهای بسیار رایج در هنگام استفاده از <Stack <T چک کردن خاصیت Count پشته است که اگر بزرگتر از صفر باشد یک عنصر از آن Pop کن و آن آیتم را استفاده کن، به طور مثال:

T item;
if (s.Count > 0)
{
item = s.Pop();
UseData(item);
}


اما در دنیایی که پشته توسط چندین Thread همزمان مورد دستیابی قرار میگیرد، حتی اگر متدهای Count و Pop به شکل Thread-Safe می بودند، ما همچنان گرفتار این مسئله می شدیم که پشته بین یک چک کردن موفق برای خالی-نبودن پشته و تلاش برای انجام یک عمل Pop، می تواند خالی شود.
<ConcurrentStack <T در API هایی که ارائه می کند این مسئله را به حساب آورده؛ برای تلاش جهت برداشتن مطمئن یک عنصر از پشته، ما میتوانیم این کد را به جای آن بنویسیم.

T item;
if (s.TryPop(out item))
{
UseData(item);
}



به این روش استفاده از <ConcurrentStack <T در یک سناریوی مشتری/تهیه‌کننده (Producer/Consumer) می تواند به شکل زیر پیاده شود:

//initialize an empty concurrent stack
ConcurrentStack<string> m_myConcurrentStack = new ConcurrentStack<string>();
//every producer will produce this number of elements
constint COUNT = 10;
publicvoid ConsumeDataFromStack()
{
//create the producers
for (int i = 0; i < COUNT; i++)
{
Thread currentProducer = newThread(newThreadStart(delegate()
{
for(int currentIndex = COUNT; currentIndex > 0; currentIndex--)
{
m_myConcurrentStack.Push(
Thread.CurrentThread.ManagedThreadId.ToString() + "_" +
currentIndex.ToString());
}
}));
currentProducer.Start();
}
//allow the worker threads to start
Thread.Sleep(500);
//consume data
string currentData = "";
while (m_myConcurrentStack.TryPop(out currentData))
{
//ConsumeData(currentData);
}
}



اجرای PLINQ روی یک پشته موازی
<ConcurrentStack <T به عنوان یک <IEnumerable<T (http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx) می تواند به عنوان منبع داده ها، در کوئری های PLINQ نیز استفاده شود.
در زیر نمونه ای از چنین استفاده‌ای وجود دارد:

volatilebool m_producersEnded = false;
constint COUNT = 10;
publicvoid PLinqOverConcurrentStack()
{
//create the producers
Thread[] producers = newThread[COUNT];
for (int i = 0; i < COUNT; i++)
{
Thread currentProducer = newThread(newThreadStart(delegate()
{
for(int currentIndex = COUNT; currentIndex > 0; currentIndex--)
{
m_myConcurrentStack.Push(currentIndex.ToString());
}
}));
producers[i] = currentProducer;
}
Thread PLINQConsumer = newThread(newThreadStart(delegate()
{
while (!m_producersEnded)
{
var currentValues = from data in m_myConcurrentStack
where data.Contains("9") select data;
foreach (string currentData in currentValues)
{
//consume data
}
}
}));
//start the consumer
pLinqConsumer.Start();
//start the producers
foreach (Thread producer in producers)
{
producer.Start();
}
//join the producers and the consumer
foreach (Thread producer in producers)
{
producer.Join();
}
m_producersEnded = true;
pLinqConsumer.Join();
}


+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
پیشنهاد برای مطالعه بیشتر در زمینه مشابه:


Parallel Programming in the .NET Framework (http://msdn.microsoft.com/en-us/library/dd460693.aspx)
Parallel Extensions to the .NET 4.0 Runtime (http://www.codeproject.com/Articles/72235/Parallel-Extensions-to-the-NET-4-0-Runtime)