MCP კლუბის 23.04.2010 - ის დეველოპერულ შეხვედრასთან დაკავშირებით მინდოდა განმეხილა Multithreading - ის საკითხები ცოცხალი მაგალითებით. როგორც გვახსოვს Multithreading - ი მოიცავს 3 ძირითად საპრობლემო საკითხს:
- DeadLock - გამოუვალი მდგომარეობა
- Race Condition - შეჯიბრის პირობა
- Starvation - შიმშილობა
განვიხილოთ თითოული მათგანი უფრო ღრმად დეტალებში
DeadLock - გამოუვალი მდგომარეობა
მაგალითად გვაქვს ორი სტატიკური ცვლადი
a და
b, რომლებზეც აპირებს მუშაობას ორი Thread - ი
ThreadProc1() და
ThreadProc2() ორივე ThreadProc ცვლადებზე მუშაობის დაწყების წინ შეეცდება ჯერ დაირეზერვოს (ანუ დაბლოკოს) ცვლადი,
Monitor.TryEnter - ით, სხვა Thread - თვის და შემდეგ დაიწყოს მასზე მუშაობა, ხოლო თუ ცვლადი უკვე დარეზერვებული აღმოჩნდა სხვა პროცესის მიერ, მუშაობა შეწყდება და TryEnter დააბრუნებს
false - ს შემდეგი ორი მაგალითის მიხედვით გამოჩნდება თუ როგორ შეიძლება დაირეზერვოს ორმა Thread - მა თითოული ცვლადი ისე რომ ერთმანეთს ხელი შეუშალონ და ვერასდროს შეასრულონ თავიანთი საქმე, ანუ შექმნან DeadLock - გამოუვალი მდგომარეობა და როგორ უნდა დაიწეროს კოდი ისე, რომ ავიცილოთ მსგავსი სიტუაცია ან როგორ უნდა დაიწეროს კოდი სწორად.
შეცდომაა 
public static class Program
{
public static Random random = new Random();
public static object a = new object();
public static object b = new object();
public static void ThreadProc1()
{
for (; ; )
{
if (Monitor.TryEnter(a, 1000) && Monitor.TryEnter(b, 1000))
{
Thread.Sleep(random.Next(500));
Monitor.Exit(b);
Monitor.Exit(a);
}
else
{
Console.WriteLine("BRUTAL NINJA KILLS THREAD 1");
}
}
}
public static void ThreadProc2()
{
for (; ; )
{
if (Monitor.TryEnter(b, 1000) && Monitor.TryEnter(a, 1000))
{
Thread.Sleep(random.Next(500));
Monitor.Exit(a);
Monitor.Exit(b);
}
else
{
Console.WriteLine("BRUTAL NINJA KILLS THREAD 2");
}
}
}
public static void Main(string[] args)
{
(new Thread(ThreadProc1)).Start();
(new Thread(ThreadProc2)).Start();
Console.WriteLine("Press <Enter> to exit.");
Console.ReadLine();
}
}
რადგან ThreadProc1() - ის
Monitor.TryEnter(a, 1000) დაბლოკავს a - ს, ამ დროს ThreadProc2() - ის
Monitor.TryEnter(b, 1000) დაბლოკავს b - ს, რის შემდეგაც შემოწმდებას ThreadProc1() - ის
if - ის მეორე პირობა TryEnter(b, 1000) რაც დააბრუნებს
false - ს და ThreadProc2() - ის
if - ის მეორე პირობა TryEnter(a, 1000) რაც აგრეთვე დააბრუნებს
false - ს. შედეგი ორივე პროცესის
if პირობა ყოველთვის გადავა
else - ში.
შედეგი DeadLock - ი:
გამოსავალი?
სწორია 
public static class Program
{
public static Random random = new Random();
public static object a = new object();
public static object b = new object();
public static void ThreadProc1()
{
for (; ; )
{
if (Monitor.TryEnter(a, 1000) && Monitor.TryEnter(b, 1000))
{
Thread.Sleep(random.Next(500));
Monitor.Exit(b);
Monitor.Exit(a);
}
else
{
Console.WriteLine("BRUTAL NINJA KILLS THREAD 1");
}
}
}
public static void ThreadProc2()
{
for (; ; )
{
if (Monitor.TryEnter(a, 1000) && Monitor.TryEnter(b, 1000))
{
Thread.Sleep(random.Next(500));
Monitor.Exit(a);
Monitor.Exit(b);
}
else
{
Console.WriteLine("BRUTAL NINJA KILLS THREAD 2");
}
}
}
public static void Main(string[] args)
{
(new Thread(ThreadProc1)).Start();
(new Thread(ThreadProc2)).Start();
Console.WriteLine("Press <Enter> to exit.");
Console.ReadLine();
}
}
Race Condition - შეჯიბრის პირობა
public static class Program
{
private static long result;
public static void ThreadProc1()
{
for (long i = 0; i < 10000000; i++)
{
// NON-ATOMIC OPERATION
result += i;
}
}
public static void ThreadProc2()
{
for (long i = 0; i < 10000000; i++)
{
// NON-ATOMIC OPERATION
result += i;
}
}
public static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread thread1 = new Thread(ThreadProc1);
Thread thread2 = new Thread(ThreadProc2);
result = 0;
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine("Try: {0}; Result: {1}", i, result);
}
}
}
თითოული 10 ცდის შედეგი იქნება განსხვავებული რადგან result ცვლადში მნიშვნელობათა ჩაწერა პრაქტიკულად "მოსწრებაზეა".
Starvation - შიმშილობა
ლოკალური ცვლადების გამოყენება vs გლობალური ცვლადების გამოყენება. მიმდევრობითი პროცესის ჯამი vs Thread - ებით დაჯამება.
public static class Program
{
//
// CLOSE IM MEMORY variables
//
private const long max = 200000000;
private static long result1;
private static long result2;
public static void SlowThreadProc1()
{
for (long i = 0; i < max; i++)
{
result1 += i;
}
}
public static void SlowThreadProc2()
{
for (long i = 0; i < max; i++)
{
result2 += i;
}
}
public static void FastThreadProc1()
{
long temp = 0;
for (long i = 0; i < max; i++)
{
temp += i;
}
result1 = temp;
}
public static void FastThreadProc2()
{
long temp = 0;
for (long i = 0; i < max; i++)
{
temp += i;
}
result2 = temp;
}
public static void Main(string[] args)
{
Stopwatch watch = new Stopwatch();
watch.Start();
SlowThreadProc1();
SlowThreadProc2();
watch.Stop();
Console.WriteLine(
"Base Time: {0}; Result: {1}",
watch.ElapsedMilliseconds.ToString().PadLeft(5),
result1 + result2);
Thread slowThread1 = new Thread(SlowThreadProc1);
Thread slowThread2 = new Thread(SlowThreadProc2);
result1 = 0;
result2 = 0;
watch.Reset();
watch.Start();
slowThread1.Start();
slowThread2.Start();
slowThread1.Join();
slowThread2.Join();
watch.Stop();
Console.WriteLine(
"Slow Time: {0}; Result: {1}",
watch.ElapsedMilliseconds.ToString().PadLeft(5),
result1 + result2);
Thread fastThread1 = new Thread(FastThreadProc1);
Thread fastThread2 = new Thread(FastThreadProc2);
result1 = 0;
result2 = 0;
watch.Reset();
watch.Start();
fastThread1.Start();
fastThread2.Start();
fastThread1.Join();
fastThread2.Join();
watch.Stop();
Console.WriteLine(
"Fast Time: {0}; Result: {1}",
watch.ElapsedMilliseconds.ToString().PadLeft(5),
result1 + result2);
}
}
თითოული ზემოთ განხილული კოდისთვის თეორიული ცოდნის მისაღებად იხილეთ პრეზენტაცია და ვიდეომასალა
mcp.community.ge Video on Vimeo source კოდები მოგვაწოდა ბატონ
რომან აკოფოვმა