Thread Synchronization

जब हम .NET Based Multi-Threaded Applications Create करते हैं, तब विभिन्न Threads के बीच Share होने वाले Data को विभिन्न Threads Change कर सकते हैं। यानी किसी AppDomain के सभी Threads Concurrently यानी समानान्तर रूप से उस AppDomain के विभिन्न Data को Share करते हुए Access कर सकते हैं।

परिणामस्वरूप कई बार ऐसी स्थिति पैदा हो जाती है, जिससे विभिन्न Threads एक ही समय पर Concurrent रूप से समान Data को ही Access कर रहे होते हैं, जहां Concurrency की वजह से वह Data Corrupt हो जाता है।

जब हम Multiple Threads Use करते हैं, तब Concurrency से पैदा होने वाली समस्या से बचने के लिए कई बार हमें दो या दो से अधिक Threads की Activities को Coordinate करने की जरूरत पडती है। Threads को Coordinate करने की इस प्रक्रिया को ही Synchronization के नाम से जाना जाता है।

Synchronization को Use करने का Most Common कारण ये है कि दो या दो से ज्यादा Threads समान Resource को Share कर सकें लेकिन समान समय पर नहीं। यानी एक समय पर केवल एक ही Thread उस Shared Resource को Access कर सके, ऐसी व्‍यवस्था करने की प्रक्रिया ही Serialization है।

उदाहरण के लिए जब एक Thread किसी File में कोई Data Write कर रहा होता है, ठीक उसी समय कोई दूसरा Thread उसी File में Data Write नहीं करना चाहिए, अन्‍यथा वह File Corrupt हो जाएगी। इसलिए ये जरूरी है कि दोनों Threads उस समान File को समान समय पर Access न करें बल्कि Serial-Wize One by One अलग-अलग Access करें।

एक और Situation होती है, जहां Serialization की जरूरत पडती है और वह Situation ये है कि जब कोई एक Thread किसी अन्‍य Thread द्वारा किसी Event के Trigger होने का Wait करता है। इस स्थिति में कोई ऐसा Mechanism होना जरूरी होता है कि जब तक दूसरा Thread वह Particular Event Trigger न करे, तब तक पहला Thread Suspended रहे और दूसरे Thread द्वारा Appropriate Event Trigger होने के बाद ही पहला Thread Resume हो।

Synchronization का मूल आधार Lock Concept है, जो कि किसी Object के किसी Code को Execute होने से किसी Specific Situation तक Block कर देता है। जब कोई Object किसी एक Thread द्वारा Locked होता है, तब कोई भी अन्‍य Thread उस Object के Locked Code Block को Access नहीं कर सकता। जब वह Thread Lock से Release होता है तभी अन्‍य Threads उस Locked Code Block को Access कर सकते हैं।

इस Lock Feature को C# में ही Built-In किया गया है। अत: C# के सभी Objects Synchronized हो सकते हैं। Synchronization को C# में lock Keyword द्वारा Support किया गया है। चूंकि Synchronization को C# में शुरूआत से ही Design किया गया था, इसलिए इसे Use करना काफी आसान होता है। वास्तव में ज्यादातर Programs में Object का Synchronization लगभग Transparent होता है। Lock को सामान्‍यत: निम्नानुसार Represent किया जाता है:

lock(lockObj) {
// statements to be synchronized
}

जहां lockObj उस Object का Reference है जिसे Synchronized किया जा रहा है। जबकि यदि हमें केवल एक ही Line के Code को Synchronize करना हो, तो हमें Curly Braces Use करने की जरूरत नहीं होती।

lock Statement इस बात को Ensure करता है कि जिस Code को हमने lock Block में Specify किया है, वह Code Lock से Protected है और उस Code को केवल वही Thread Use कर सकता है, जिसके पास उस Locked Code का Lock Available है।

जबकि अन्‍य सभी Threads उस Code को तब तक Use नहीं कर सकते, जब तक कि उससे Lock को Remove न कर दिया जाए और Lock तब Release होता है जब Program Control उस Lock Block से बाहर निकलता है।

जिस Object पर हम Lock Specify करते हैं, वह ऐसा Object होता है जो कि उन Resources को Represent करता है, जिन्हें Synchronize किया जा रहा है। कई परिस्थितियों में तो ये Object स्वयं उस Resource का Instance होता है, जिसे Synchronize करना है या फिर एक object Type का Instance होता है, जिसका प्रयोग Synchronization के लिए किया जाता है।

lock के सन्दर्भ में जो सबसे मुख्‍य समझने वाली बात है वो ये है कि जिस Object पर Lock होता है, उस Object को Publicly Access नहीं किया जा सकता। क्योंकि ये भी सम्भव है कि कोई ऐसा Code भी हो सकता है, जो किसी Object को Lock कर दे, लेकिन कभी भी Release न करे और उस पर हमारा Control भी न हो।

lock System को Use करते हुए Concurrency की समस्या से बचने के लिए हम Serialization के Concept को निम्न Demo Program के आधार पर ज्यादा बेहतर तरीके से समझ सकते हैं:

File Name: ConcurrencyIssue.cs
using System;
using System.Threading;

namespace CSharpMultiThreading
{
    class Program
    {
        static readonly object _object = new object();

        static void CommonMethod()
        {
            Thread.Sleep(100);
            Console.WriteLine("CommonMethod executing for Secondary Thread#{0} on TickCount: 
	{1}", Thread.CurrentThread.ManagedThreadId, Environment.TickCount);
        }

        static void Main()
        {
            // Create and Start ten new threads.
            for (int i = 0; i < 10; i++)
            {
                new Thread(CommonMethod).Start();
            }
        }
    }
}

Output:
CommonMethod executing for Secondary Thread#3 on TickCount: 416151433
CommonMethod executing for Secondary Thread#4 on TickCount: 416151433
CommonMethod executing for Secondary Thread#5 on TickCount: 416151433
CommonMethod executing for Secondary Thread#6 on TickCount: 416151433
CommonMethod executing for Secondary Thread#7 on TickCount: 416151433
CommonMethod executing for Secondary Thread#8 on TickCount: 416151449
CommonMethod executing for Secondary Thread#9 on TickCount: 416151449
CommonMethod executing for Secondary Thread#12 on TickCount: 416151449
CommonMethod executing for Secondary Thread#11 on TickCount: 416151449
CommonMethod executing for Secondary Thread#10 on TickCount: 416151449

इस Program में हमने Main() Method में एक Loop में 10 Secondary Threads Create किए हैं और सभी Threads CommonMethod() नाम के एक Static Method को Invoke कर रहे हैं।

जब हम इस Program को Run करते हैं, तो जैसाकि हम इस Program के Output में देख सकते हैं, कि सभी Threads क्रम से Create होते हैं और Thread#3 से Thread#7 तक एक ही समय पर CommonMethod() को Invoke करते हैं। जबकि Thread#8 से लेकर Thread#12 तक के सभी Threads समान समय पर CommonMethod() को Invoke कर रहे हैं।

जो इसी बात का Indication है कि एक Process के विभिन्न Secondary Threads समान समय पर एक ही Method को Use कर रहे हैं, जिसकी वजह से Concurrency Issue होने की सम्भावना है। जबकि यदि हम चाहते हैं कि Concurrency Issue पैदा न हो, तो हम इस Program को निम्नानुसार तरीके से lock Concepts का प्रयोग करते हुए Modify कर सकते हैं:

File Name: ConcurrencyControlWithLocks-Synchronization.cs
using System;
using System.Threading;

namespace CSharpMultiThreading
{
    class Program
    {
        static readonly object _object = new object();

        static void CommonMethod()
        {
            // Lock on the readonly object.
            // ... Inside the lock, sleep for 100 milliseconds.
            lock (_object)
            {
                Thread.Sleep(50);
                Console.WriteLine("CommonMethod executing for Secondary Thread#{0} on TickCount: {1}", Thread.CurrentThread.ManagedThreadId, Environment.TickCount);
            }
        }

        static void Main()
        {
            // Create and Start ten new threads.
            for (int i = 0; i < 10; i++)
            {
                new Thread(CommonMethod).Start();
            }
        }
    }
}
Output:
CommonMethod executing for Secondary Thread#3 on TickCount: 418013103
CommonMethod executing for Secondary Thread#4 on TickCount: 418013149
CommonMethod executing for Secondary Thread#5 on TickCount: 418013212
CommonMethod executing for Secondary Thread#7 on TickCount: 418013259
CommonMethod executing for Secondary Thread#9 on TickCount: 418013305
CommonMethod executing for Secondary Thread#11 on TickCount: 418013352
CommonMethod executing for Secondary Thread#6 on TickCount: 418013415
CommonMethod executing for Secondary Thread#8 on TickCount: 418013461
CommonMethod executing for Secondary Thread#10 on TickCount: 418013508
CommonMethod executing for Secondary Thread#12 on TickCount: 418013555

यदि हम इस Program के Output को देखें तो आसानी से समझ सकते हैं कि हालांकि Main() Method एक साथ 10 Threads Create कर रहा है, लेकिन एक समय पर केवल एक ही Thread CommonMethod() को Invoke कर रहा है। इसीलिए Output में हमें हर Thread का Execution Time अलग दिखाई दे रहा है।

इन दोनों Programs में हमने TickCount नाम की एक Property को Use किया है। इस Property का मान 1 Second = 10000000 Ticks होता है। यानी इस Property का प्रयोग करके हम हमारे Computer में 1 Second के एक करोडवें हिस्से के अन्तर पर Trigger होने वाले एक से ज्यादा Events को अलग-अलग Identify कर सकते हैं।

हमने Environment.Ticks Property को इसीलिए Use किया है, ताकि हम निश्चित रूप से जान सकें कि कौनसा Thread CommonMethod को किस Tick Number पर Invoke कर रहा है। ताकि हमें पता चल सके कि एक ही Program के विभिन्न Threads Synchronization की स्थिति में Common Method को भी Serial-Wise One-by-One Invoke करते हैं, जबकि Un-Synchronization की स्थिति में एक ही समान Tick पर एक से ज्यादा Threads समान Method को Invoke कर सकते हैं, जैसाकि पिछले Program के Output में हम देख सकते हैं।

जब हम हमारे किसी Thread Code को अन्‍य Threads के बीच Synchronize तरीके से Share करवाना चाहते हैं, तब हम उस Code को lock Block के बीच Specify करके Lock कर देते हैं, जैसाकि उपरोक्त Program में निम्नानुसार Code द्वारा किया गया है:

            lock (_object)
            {
                Thread.Sleep(50);
                Console.WriteLine("CommonMethod executing for Secondary Thread#{0} on TickCount: {1}", Thread.CurrentThread.ManagedThreadId, Environment.TickCount);
            }

ये Code केवल उसी स्थिति में Execute होता है, जबकि Currently जो Thread इस Code को Execute कर रहा है, वह इस Code को Release कर दे और Current Thread इस Code को तब Release करता है, जब या तो वह इस Code को पूरी तरह से Execute कर चुका हो अथवा Thread.Sleep() जैसे किसी Method के Invocation के कारण Block यानी Suspended Mode में चला गया हो।

लेकिन जब हम lock Block का प्रयोग करते हैं, तब हमें हमेंशा object Type के एक Private Object को इस lock Block के Parenthesis के बीच Token की तरह Specify करना जरूरी होता है, जिससे C# Compiler को इस बात का Indication मिलता है कि हम हमारे Current Code Block को विभिन्न Threads के बीच एक Synchronized तरीके से Share करवाना चाहते हैं और इस Token के रूप में हमने हमारे Program में निम्नानुसार एक Read-Only Private Object को Declare किया है:

static readonly object _object = new object();

हम हमारे किसी भी Program में इस Statement को Use कर सकते हैं, जिसमें हम Synchronization की सुविधा प्राप्त करना चाहते हैं। यानी इस Statement का कोई Special Meaning नहीं है न ही ये Object पूरे Program में कहीं और काम आ रहा है। ये Object केवल C# Compiler के लिए एक Indication मात्र है, कि Current Thread तब तक Locked Code को Access नहीं कर सकताए जब तक कि दूसरा Thread Current Code को Release हीं कर सकता।

System.Threading.Monitor Type for Synchronization

हालांकि हम C# lock Block का प्रयोग करके आसानी से Synchronization की सुविधा प्राप्त कर सकते हैं जो कि System.Threading.Monitor Class का ही Shorthand Notation है। लेकिन जब C# Compiler हमारे lock Statement को Execute करता है, तो वह वास्तव में Internally निम्न तरीके से Resolve होता है:

File Name: ConcurrencyControlWithLocksExpanded.cs

. . .
static void CommonMethod()
{
	Monitor.Enter(_object);
	try
	{
		Thread.Sleep(50);
		Console.WriteLine("CommonMethod executing for Secondary Thread#{0} on TickCount: {1}", Thread.CurrentThread.ManagedThreadId, Environment.TickCount);
	}
	finally
	{
		Monitor.Exit(_object);
	}
}
. . .

यानी यदि हम चाहें तो हमारे पिछले Program के CommonMethod() नाम के Method के Codes को हम इस Code से भी Replace कर सकते हैं और यदि हम ऐसा करते हैं, तो भी हमारे Program की Working पर कोई प्रभाव नहीं पडता क्योंकि lock Statement Internally इसी Format में Expand होता है।

यदि हम उपरोक्त Code को समझें तो lock Statement के Parenthesis में हम जिस Token को Specify करते हैं, वह Token निम्नानुसार Statement के रूप में Monitor.Enter() Method को Parameter की तरह Pass हो जाता है:

Monitor.Enter(_object);

फिर हमने हमारे lock Statement Block में जो भी Statements लिखे हैं, वे सभी try Block के Statements बन जाते हैं और निम्नानुसार तरीके से finally Block में Specified Monitor.Exit() Method में भी हमारा Token Pass हो जाता है:

Monitor.Exit(_object);

इसलिए यदि हम चाहें तो lock Statement को Use करने के स्थान पर उपरोक्तानुसार तरीके से System.Threading.Monitor Type को Use करते हुए भी अपना Synchronization Code लिख सकते हैं। क्योंकि lock Statement Execution के दौरान इसी Format में उपरोक्तानुसार ही Resolve होता है।

दोनों ही प्रकार के Codes की Internal Working में कोई अन्तर नहीं है, लेकिन जब हम इन्हें Use करते हैं, तो इन्हें Use करने के तरीके में काफी अन्तर हो जाता है। उदाहरण के लिए जब हम lock Statement Use करते हैं, तब Thread को Waiting State में भेजने के लिए हमें Thread.Sleep() Method को Use करना होता है। जबकि यदि हम Monitor Type को Use करें, तो इसी जरूरत को पूरा करने के लिए हमें Monitor.Wait() Method को Use करना पडता है।

इसी तरह से जब हमें Wait करने वाले Thread को इस बात का Instruction देता है कि Current Thread ने अपना काम Complete कर लिया है, तो ये Instruction देने के लिए हमें Monitor.Pulse() Monitor.PulseAll() Method को Use करना होता है।

हालांकि हम Monitor Class को उसकी पूरी क्षमता के साथ Use करने के लिए उसके विभिन्न Methods व Members को जरूरत के अनुसार उपयोग में ले सकते हैं और Monitor Class द्वारा Provided विभिन्न Members की जानकारी प्राप्त करने के लिए Visual Studio के Object Browser utility को Use कर सकते हैं।

लेकिन फिर भी हमारी विभिन्न प्रकार की जरूरतों को lock Statement द्वारा आसानी से पूरा किया जा सकता है। इसलिए हम सामान्‍यत: Synchronization के लिए lock Statement को ही उपयोग में लेते हैं।

[Synchronization] Attribute for Synchronization

जब हम हमारे किसी Program में [Synchronization] Attribute को Use करते हैं, जो कि System.Runtime.Remoting.Contexts Namespace का एक Member है। तो उस Program के सभी Objects के Instance Member Codes Thread Safety के लिए पूरी तरह से Lock हो जाते हैं।

इसलिए जब CLR किसी [Synchronization] Attribute वाले Class के Objects को Memory Allocate करता है, तो वह उन सभी Objects को Synchronized Context में Place करता है। परिणामस्वरूप जिन Objects को Contextual Boundary से Remove नहीं करना होताए उन्हें ContextBoundObject से Derive किया जाता है। जैसे:

	using System.Runtime.Remoting.Contexts;
	. . .

	// All methods of This class are now thread safe!
	[Synchronization]
	public class ThreadSafeClass : ContextBoundObject
	{
		public void Display()
		{
			. . . 
		}
	}

हालांकि Thread-Safe Code Create करने का ये सबसे आसान तरीका है। लेकिन इस तरीके को Use करने पर यदि किसी Method में Thread-Sensitive Data न भी हो, तब भी CLR हर Thread के लिए उस Method को Lock कर देता है, जिसकी वजह से उस Particular Type की Performance प्रभावित होती है, जिसमें इस Attribute को Use किया गया होता है।

C# in Hindiये Article इस वेबसाईट पर Selling हेतु उपलब्‍ध EBook C#.NET in Hindi से लिया गया है। इसलिए यदि ये Article आपके लिए उपयोगी रहा, तो निश्चित रूप से ये पुस्तक भी आपके लिए काफी उपयोगी साबित होगी। 

C#.NET in Hindi | Page:908 | Format: PDF

BUY NOW GET DEMO REVIEWS