The process of creating a thread is called spawning a new thread, the process of destroying an existing thread is called killing or terminating a thread, and the process of pausing a thread is called putting the thread to sleep.
A short-lived thread is a thread that is created to perform a single task and is then terminated. It is common to create short-lived threads to perform operating system tasks such as copying or moving files. In such situations, you can use the ThreadPool class in the .NET Framework. This class can also be used when it is not necessary to have full control over each individual thread.
The ThreadPool class manages a set of readily available threads to be used by a process. Each process has only one thread pool that the process uses to execute different methods. Resource allocation does not need to occur every time a thread is started because the thread is already created in the pool. This increases application performance. However, there are situations in which the thread pool should be avoided because there is no direct control over the thread. You should not use a thread pool when:
You require a foreground thread.
You require a thread to have a particular priority.
You have tasks that can block threads for long periods of time. The thread pool has a maximum number of threads, so a large number of threads from the thread pool being blocked might prevent tasks from starting.
You need to place threads into a single-threaded apartment. All thread pool threads are in the multithreaded apartment.
You need to have a stable identity associated with the thread, or you need to dedicate a thread to a task.
Based on these situations, you need to use a thread pool whenever the application requires threads to execute short-lived tasks in a fast-paced scenario. For example, if you want to test read/write operations to a database simulating a multiuser environment, you can create a set of methods that access the database by executing common queries, and use multiple threads from the thread pool to execute the methods multiple times, simultaneously.
There is a limit of 25 threads in a thread pool; therefore, even if you start 50 threads, only 25 threads will be executed at a given time. As soon as a thread finishes, another thread that was waiting for an idle thread in the pool can start.
The process of managing access to resources being shared by multiple threads is called synchronization.Synchronization comprises of locking mechanisms, signaling, and some interlocked operations.
the most common ones include exclusive and shared locks. The .NET Framework provides several classes that you can use to lock resources. However, the simplest method to lock a shared resource is to use the SyncLock statement in Visual Basic or the lock statement in C#.
When a thread notifies another thread about the lock status of a resource, it is called signaling. synchronization includes some interlocked operations that you can use to perform simple operations on a shared memory location or a variable.
.NET Framework provides several classes to manage the locking of shared resources. These classes include Monitor, WaitHandle, Mutex, Semaphore, and ReaderWriterLock.
The Monitor class is a static class used by a thread to acquire an exclusive lock on an object so that other threads are not able to use that object while it is being monitored. The use of the Monitor class is similar to that of the SyncLock or lock blocks in Visual Basic and C# respectively. To start synchronization, the thread must call the Enter method of the Monitor class and pass the object to be locked as a parameter. To end synchronization, the thread must call the Exit method and pass the same parameter. The code is synchronized between the calls. Although the functionality of the Monitor class is exactly the same as that of the SyncLock and lock blocks, this class provides better control of the synchronization code through a set of methods.
The Mutex class derives from the WaitHandle class and provides the same functionality achieved through the Monitor class, but it can synchronize threads in different application domains. There are two types of mutexes that can be created, local and system mutexes. A local mutex can only be used within the process where it was created, while a system (or named) mutex can be used by any process in the operating system. Once a mutex is created, it can only be released from the thread in which it was created.
You can use the Semaphore class to synchronize access to resources by a pool of threads, instead of a single thread at once. When using the Semaphore class, you can specify the maximum number of threads that can access the resources simultaneously. Other threads must wait in queue until the executing threads release the semaphore. You can achieve this functionality by maintaining a count of new threads that can access the resource, decrementing this count every time a thread enters the semaphore, and incrementing it every time a thread leaves the semaphore. Whenever the counter is greater than zero, new threads can enter the semaphore. Once the counter is zero, incoming threads will be queued until the counter increases. Just like Mutex, Semaphore can be local or global (named).
You can use the Semaphore class when you want to restrict the number of threads executing a piece of code.
You can use the ReaderWriterLock class to synchronize access to resources by using a pool of threads with read access or a single thread with write access. You can use different methods in the ReaderWriterLock class to lock resources for either read or write access. When a thread submits a read lock request, the ReaderWriterLock class works similar to the Semaphore class, allowing access to resources by multiple threads. Once a writer lock is requested, all new reader requests are queued. After the existing locks are released, the writer lock is honored. A lock can be represented by a variable of type LockCookie for multiple requests, such as when upgrading a reader lock to a writer lock and vice versa. However, a thread cannot have both reader and writer locks at the same time; it must change the lock type by using predefined methods.
You can use the ReaderWriterLock class in situations where changes to resources are infrequent and multiple threads are able to share the resource.
When working with the Monitor class, you can implement signaling by using the Pulse method. However, you can also use other classes such as Mutex, Semaphore, EventWaitHandle, and RegisteredWaitHandle for signaling.
Signaling may be necessary when the same object is being used to execute different actions by different threads, and the synchronization of these different threads is needed. For instance, consider a bank account object being used simultaneously to both withdraw and deposit money in an account. The deposit method reads the balance, followed by the withdraw method. The withdraw method then changes the balance based on the amount withdrawn. Since the deposit method had already read the balance, it adds the amount to be deposited to the old balance, and therefore, saves the new balance incorrectly. This error could be prevented if the two threads were using signaling.
All classes that derive from the WaitHandle class can use signaling, such as the Mutex, Semaphore, and EventWaitHandle classes.
You can use the EventWaitHandle class to exchange signals among different threads and processes. EventWaitHandle can be local (to the process) or global (named). The EventWaitHandle class has two child classes, AutoResetEvent and ManualResetEvent. These two classes are very similar; you can use both to signal to a thread whether it can execute or not after waiting for a signal.
You can think of the signal as a green (go) or red (stop) light letting the thread know whether to wait or move on. Once a thread makes a call to wait on a signal, it stops until a green light is received. The signal remains green until a thread turns it red again. This describes the Set (turn green) and Reset (turn red) methods available in the ManualResetEvent class. The AutoResetEvent class works in a similar way; however, the light turns red automatically after turning green and letting one, and only one, thread continue.
You can use the AutoResetEvent class in the same way as the EventWaitHandle class when working with an object in the AutoReset mode. The only difference is that the event created is a local event, with thread affinity. You can use this class when the signal does not need to cross application domains and the AutoResetEvent class automatically resets itself between signaled and non-signaled states. The only constructor for this class takes a parameter of type Boolean indicating if the initial state of the event is signaled.
You can use the ManualResetEvent class in the same way as the EventWaitHandle class when working with an object in the ManualReset mode. The only difference is that the event created is a local event, with thread affinity. You should use this class when the signal does not need to cross application domains. The ManualResetEvent class remains signaled until you manually switch it to a non-signaled state and vice versa.
The only constructor for the ManualResetEvent class takes a parameter of type Boolean indicating if the initial state of the event is signaled. You can use the OpenExisting method of the ManualResetEvent class to open a named event that already exists.
When attempting to access a shared resource by using a thread from a thread pool, your application may have to wait in queue until the resource is free. Your application can reserve a place in queue in a method analogous to you taking a numeric ticket to stand in queue while at a public office. The ThreadPool class includes a method named RegisterWaitForSingleObject that returns a WaitHandle class. WaitHandle represents your application’s reserved spot in the queue and your object is signaled when the resource in question is free.
The RegisteredWaitHandle class represents the WaitHandle returned by the RegisterWaitForSingeObject method. If you decide that you would like your application to surrender its place in line and no longer wait for a resource, you may call the Unregister method.
The Interlocked class is a static (Shared) class that you can use to synchronize shared thread access to a variable. Variables that are interlocked have an exclusive lock placed on them so that no other threads can interrupt operations. The operations that can be performed on an interlocked variable are limited to very simple operations such as simple mathematic calculations. One benefit of using the Interlocked class is that it performs operations at a very low level, directly with the operating system and is, therefore, very fast.
In asynchronous programming, calls to various methods are made in a parallel manner where a method call does not wait for the other method to complete before application execution continues.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment