tooraj_azizi_1035
سه شنبه 13 تیر 1391, 03: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();
}
یک مشکل رایج که کاربران در هنگام نوشتن برنامه های موازی با آن درگیر هستند فقدان وجود 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();
}