Thread in C# - Part 3: Synchronization Essentials  
 

(Post 09/10/2007) The following tables summarize the .NET tools available for coordinating or synchronizing the actions of threads:

Simple Blocking Methods

Construct Purpose
Sleep Blocks for a given time period
Join Waits for another thread to finish

Locking Constructs

Construct Purpose Cross-Process? Speed
lock Ensures just one thread can access a resource, or section of code no fast
Mutex Ensures just one thread can access a resource, or section of code.Can be used to prevent multiple instances of an application from starting yes moderate
Semaphore Ensures not more than a specified number of threads can access a resource, or section of code. yes moderate

(Synchronization Contexts are also provided, for automatic locking).

Signaling Constructs

Construct Purpose Cross-Process? Speed
EventWaitHandle Allows a thread to wait until it receives a signal from another thread yes moderate
Wait and Pulse* Allows a thread to wait until a custom blocking condition is met no moderate

Non-Blocking Synchronization Constructs*

Construct Purpose Cross-Process? Speed
Interlocked* To perform simple non-blocking atomic operations yes (assuming shared memory) very fast
volatile* To allow safe non-blocking access to individual fields outside of a lock very fast

*Covered in Part 4

Blocking

When a thread waits or pauses as a result of using the constructs listed in the tables above, it's said to be blocked. Once blocked, a thread immediately relinquishes its allocation of CPU time, adds WaitSleepJoin to its ThreadState property, and doesn’t get re-scheduled until unblocked. Unblocking happens in one of four ways (the computer's power button doesn't count!):

  • by the blocking condition being satisfied
  • by the operation timing out (if a timeout is specified)
  • by being interrupted via Thread.Interrupt
  • by being aborted via Thread.Abort

A thread is not deemed blocked if its execution is paused via the (deprecated) Suspend method.

Sleeping and Spinning

Calling Thread.Sleep blocks the current thread for the given time period (or until interrupted):

static void Main() {
Thread.Sleep (0); // relinquish CPU time-slice
Thread.Sleep (1000); // sleep for 1000 milliseconds
Thread.Sleep (TimeSpan.FromHours (1)); // sleep for 1 hour
Thread.Sleep (Timeout.Infinite); // sleep until interrupted
}

More precisely, Thread.Sleep relinquishes the CPU, requesting that the thread is not re-scheduled until the given time period has elapsed. Thread.Sleep(0) relinquishes the CPU just long enough to allow any other active threads present in a time-slicing queue (should there be one) to be executed.

Thread.Sleep is unique amongst the blocking methods in that suspends Windows message pumping within a Windows Forms application, or COM environment on a thread for which the single-threaded apartment model is used. This is of little consequence with Windows Forms applications, in that any lengthy blocking operation on the main UI thread will make the application unresponsive – and is hence generally avoided – regardless of the whether or not message pumping is "technically" suspended. The situation is more complex in a legacy COM hosting environment, where it can sometimes be desirable to sleep while keeping message pumping alive. Microsoft's Chris Brumme discusses this at length in his web log (search: 'COM "Chris Brumme"').

The Thread class also provides a SpinWait method, which doesn’t relinquish any CPU time, instead looping the CPU – keeping it “uselessly busy” for the given number of iterations. 50 iterations might equate to a pause of around a microsecond, although this depends on CPU speed and load. Technically, SpinWait is not a blocking method: a spin-waiting thread does not have a ThreadState of WaitSleepJoin and can’t be prematurely Interrupted by another thread. SpinWait is rarely used – its primary purpose being to wait on a resource that’s expected to be ready very soon (inside maybe a microsecond) without calling Sleep and wasting CPU time by forcing a thread change. However this technique is advantageous only on multi-processor computers: on single-processor computers, there’s no opportunity for a resource’s status to change until the spinning thread ends its time-slice – which defeats the purpose of spinning to begin with. And calling SpinWait often or for long periods of time itself is wasteful on CPU time.

Blocking vs. Spinning

A thread can wait for a certain condition by explicitly spinning using a polling loop, for example:

while (!proceed);
or:
while (DateTime.Now < nextStartTime);

This is very wasteful on CPU time: as far as the CLR and operating system is concerned, the thread is performing an important calculation, and so gets allocated resources accordingly! A thread looping in this state is not counted as blocked, unlike a thread waiting on an EventWaitHandle (the construct usually employed for such signaling tasks).

A variation that's sometimes used is a hybrid between blocking and spinning:

while (!proceed) Thread.Sleep (x); // "Spin-Sleeping!"

The larger x, the more CPU-efficient this is; the trade-off being in increased latency. Anything above 20ms incurs a negligible overhead – unless the condition in the while-loop is particularly complex.

Except for the slight latency, this combination of spinning and sleeping can work quite well (subject to concurrency issues on the proceed flag, discussed in Part 4). Perhaps its biggest use is when a programmer has given up on getting a more complex signaling construct to work!

Joining a Thread

You can block until another thread ends by calling Join:

class JoinDemo {
static void Main() {
Thread t = new Thread (delegate() { Console.ReadLine(); });
t.Start();
t.Join(); // Wait until thread t finishes
Console.WriteLine ("Thread t's ReadLine complete!");
}
}

The Join method also accepts a timeout argument – in milliseconds, or as a TimeSpan, returning false if the Join timed out rather than found the end of the thread. Join with a timeout functions rather like Sleep – in fact the following two lines of code are almost identical:

Thread.Sleep (1000);
Thread.CurrentThread.Join (1000);

(Their difference is apparent only in single-threaded apartment applications with COM interoperability, and stems from the subtleties in Windows message pumping semantics described previously: Join keeps message pumping alive while blocked; Sleep suspends message pumping).

(Sưu tầm)


 
 

 
     
 
Công nghệ khác:


Thread in C# - Part 2: Creating and Starting ThreadsThread in C# - Part 1: Overview and Concepts
Xử lý những sự cố máy tính xấu nhấtPhòng và diệt virus USB một cách đơn giản
Tạo trình duyệt hoàn hảoTăng tuổi thọ máy tính bằng nâng cấp
  Xem tiếp    
 
Lịch khai giảng của hệ thống
 
Ngày
Giờ
T.Tâm
TP Hồ Chí Minh
Hà Nội
 
   
New ADSE - Nhấn vào để xem chi tiết
Mừng Sinh Nhật Lần Thứ 20 FPT-APTECH
Nhấn vào để xem chi tiết
Bảng Vàng Thành Tích Sinh Viên FPT APTECH - Nhấn vào để xem chi tiết
Cập nhật công nghệ miễn phí cho tất cả cựu sinh viên APTECH toàn quốc
Tiết Thực Vì Cộng Đồng
Hội Thảo CNTT
Những khoảnh khắc không phai của Thầy Trò FPT-APTECH Ngày 20-11