|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Index
1. Introduction - Native Win32 IOCPI/O Completion Ports (IOCP) supported on Microsoft Windows platforms has two facets. It first allows I/O handles like file handles, socket handles, etc., to be associated with a completion port. Any async I/O completion event related to the I/O handle associated with the IOCP will get queued onto this completion port. This allows threads to wait on the IOCP for any completion events. The second facet is that we can create a I/O completion port that is not associated with any I/O handle. In this case, the IOCP is purely used as a mechanism for efficiently providing a thread-safe waitable queue technique. This technique is interesting and efficient. Using this technique, a pool of a few threads can achieve good scalability and performance for an application. Here is a small example. For instance, if you are implementing a HTTP server application, then you need to do the following mundane tasks apart from the protocol implementation:
You can implement it by creating one dedicated thread per client connection that can continuously communicate with the client to and fro. But this technique quickly becomes a tremendous overhead on the system, and will reduce the performance of the system as the number of simultaneous active client connections increase. This is because, threads are costly resources, and thread switching is the major performance bottle neck especially when there are more number of threads. The best way to solve this is to use an IOCP with a pool of threads that can work with multiple client connections simultaneously. This can be achieved using some simple steps...
This technique will allow a small pool of threads to efficiently handle communication with hundreds of client connections simultaneously. Moreover, this is a proven technique for developing scalable server side applications on Windows platforms. The above is a simplified description of using IOCP in multithreaded systems. There are some good in-depth articles on this topic in CodeProject and the Internet. Do a bit of Googling on words like IO Completion Ports, IOCP, etc., and you will be able to find good articles. 2. Introduction - Managed IOCPManaged IOCP is a small .NET class library that provides the second facet of Native Win32 IOCP. This class library can be used both by C# and VB.NET applications. I chose the name Managed IOCP to keep the readers more close to the techniques they are used to with native Win32 IOCP. As the name highlights, Managed IOCP is implemented using pure .NET managed classes and pure .NET synchronization primitives. At its core, it provides a thread-safe object queuing and waitable object receive mechanism. Apart from that, it provides a lot more features. Here is what it does:
2.1. Managed IOCP in Job/Task Oriented Business ProcessesManaged IOCP can be used in other scenarios apart from the sample that I mentioned in the introduction to native Win32 IOCP. It can be used in process oriented server side business applications. For instance, if you have a business process ( _not_ a Win32 process) with a sequence of tasks that will be executed by several clients, you will have to execute several instances of the business process, one for each client in parallel. As mentioned in my introduction to native Win32 IOCP, you can achieve this by spawning one dedicated thread per business process instance. But the system will quickly run out of resources, and the system/application performance will come down as more instances are created. Using Managed IOCP, you can achieve the same sequential execution of multiple business process instances, but with fewer threads. This can be done by dispatching each task in a business process instance as an object to Managed IOCP. It will be picked up by one of the waiting threads and will be executed. After completing the execution, the thread will dispatch the next task in the business process instance to the same Managed IOCP, which will be picked up by another waiting thread. This is a continuous cycle. The advantage is that you will be able to achieve the sequential execution goal of a business process, as only one waiting thread can receive a dispatched object, and at the same time keep the system resource utilization to required levels. Also, the system and business process execution performance will increase as there are few threads executing multiple parallel business processes. 3. Using Managed IOCP in .NET applicationsMultithreaded systems are complex in the context that most problems will show up in real time production scenarios. To limit the possibility of such surprises while using Managed IOCP, I created a test application using which several aspects of the Managed IOCP library can be tested. Nevertheless, I look forward for any suggestions/corrections/inputs to improve this library and its demo application. Before getting into the demo application, below is the sequence of steps that an application would typically perform while using the Managed IOCP library:
3.1. Advanced usageFollowing are the advanced features of Managed IOCP that need to be used carefully. Managed IOCP execution can be paused at runtime. When a Managed IOCP instance is paused, all the threads registered with this instance of Managed IOCP will stop processing the queued objects. Also, if the ' mIOCP.Pause();
Once paused, the mIOCP.Run();
The running status of the Managed IOCP instance can be obtained using the bool bIsRunning = mIOCP.IsRunning;
You can retrieve the 3.2. Demo ApplicationI provided two demo applications with similar logic. The first is implemented using Managed IOCP, the other using native Win32 IOCP. These two demo applications perform the following steps:
The Sonic.Net (
Below is the image showing both the demo applications after their first cycle of object processing:
Demo application results As you can see in the above figure, Managed IOCP gives the same speed (slightly even better) as native Win32 IOCP. The goal of these two demo applications is _not_ to compare the speed or features of Win32 IOCP with that of Managed IOCP, but rather to highlight that Managed IOCP provides all the advantages of native Win32 IOCP (with additional features) but in a purely managed environment. I tested these two demo applications on a single processor CPU and a dual processor CPU. The results are almost similar, in the sense the Managed IOCP is performing as good as (sometimes performing better than) native Win32 IOCP. 3.3. Source and demo application filesBelow are the details of the files included in the article's Zip file:
4. Inside Managed IOCPThis section discusses the how and why part of the core logic that is used to implement Managed IOCP. 4.1. Waiting and retrieving objects in Managed IOCPManaged IOCP provides a thread safe object dispatch and retrieval mechanism. This could have been achieved by a simple synchronized queue. But with synchronized queue, when a thread (thread-A) dispatches (enqueues) an object onto the queue, for another thread (thread-B) to retrieve that object, it has to continuously monitor the queue. This technique is inefficient as thread-B will be continuously monitoring the queue for arrival of objects, irrespective of whether the objects are present in the queue. This leads to heavy CPU utilization and thread switching in the application when multiple threads are monitoring the same queue, thus degrading the performance of the system. Managed IOCP deals with this situation by attaching an auto reset event to each thread that wants to monitor the queue for objects and retrieve them. This is why any thread that wants to wait on a Managed IOCP queue and retrieve objects from it has to register with the Managed IOCP instance using its ' So in Managed IOCP, when thread-B and thread-C call the ' 4.2. Compare-And-Swap (CAS) in Managed IOCPCAS is a very familiar term in the software community, dealing with multi-threaded applications. It allows you to compare two values, and update one of them with a new value, all in a single atomic thread-safe operation. In Managed IOCP, when a thread successfully grabs an object from the IOCP queue, it is considered to be active. Before grabbing an available object from the queue, Managed IOCP checks if the number of currently active threads is less than the allowed maximum concurrent threads. In case the number of current active threads is equal to the maximum allowed concurrent threads, then Managed IOCP will block the thread, trying to receive the object from the IOCP queue. To do this, Managed IOCP has to follow the logical steps as mentioned below:
In the above logic, step-3 consists of two operations, comparison and assignment. If we perform these two operations separately in Managed IOCP, then for instance, thread-A and thread-B might both reach the conditional expression with the same would-be value for active threads. If this value is less than or equal to the maximum number of allowed concurrent threads, then the condition will pass for both the threads, and both of them will assign the same would-be value for the active threads. Though the active thread count may not increase in this scenario, the actual number of physically active threads will be more than the desired maximum number of concurrent threads, as in the above scenario both the threads think that they can be active. So Managed IOCP performs this operation as shown below:
In the above logic, the CAS operation supported by the .NET framework ( internal bool IncrementActiveThreads()
{
bool incremented = true;
do
{
int curActThreads = _activeThreads;
int newActThreads = curActThreads + 1;
if (newActThreads <= _concurrentThreads)
{
// Break if we had successfully incremented
// the active threads
if (Interlocked.CompareExchange(ref _activeThreads,
newActThreads,curActThreads) == curActThreads)
break;
}
else
{
incremented = false;
break;
}
} while(true);
return incremented;
}
I could have used a lock mechanism like 4.3. Concurrency management in Managed IOCPConcurrency is one area that native Win32 IOCP excels in. It provides a mechanism where the maximum number of allowed concurrent threads can be set during its creation. It guarantees that at any given point of time, only the maximum allowed concurrent threads are running, and more importantly, it sees to it that _atleast_ the maximum allowed concurrent threads are _always_ notified/awakened to process completion events, if the number of threads using its IOCP handle is more than the maximum number of allowed concurrent threads. Managed IOCP also provides the above two guarantees with more features like ability to modify the maximum number of allowed concurrent threads at runtime, which native Win32 IOCP does not provide. Managed IOCP provides this guarantee using the Compare-And-Swap (CAS) technique in its I could have used Win32 Semaphores to limit the maximum number of allowed concurrent threads. But it will defeat the whole purpose of Managed IOCP, being completely managed, as .NET 1.1 does not provide a Semaphore type. Also, I wanted this library to be as compatible as possible with the Mono .NET runtime. These are the reasons I did not explore the usage of semaphore for this feature. Maybe, I'll take a serious look at it if .NET 2.0 has a Semaphore object. The second feature of IOCP as described in the beginning of this section is described in more detail in the next section (dispatching objects in Managed IOCP). 4.4. Dispatching objects in Managed IOCPManaged IOCP maintains a queue of If the thread is not waiting on Below is the method that is used to choose a thread when an object is dispatched onto Managed IOCP: private void WakeupNextThread()
{
bool empty = false;
#if (DYNAMIC_IOCP)
// First check if we should service this request from suspended
// IOCPHandle queue
//
if ((_activeThreads < _concurrentThreads) &&
(_qIOCPHandle.Count >= _concurrentThreads))
{
IOCPHandle hSuspendedIOCP =
_qSuspendedIOCPHandle.Dequeue(ref empty) as IOCPHandle;
if ((empty == false) && (hSuspendedIOCP != null))
{
hSuspendedIOCP.SetEvent();
return;
}
}
empty = false;
#endif
while (true)
{
#if (LOCK_FREE_QUEUE)
IOCPHandle hIOCP = _qIOCPHandle.Dequeue(ref empty) as IOCPHandle;
#else
IOCPHandle hIOCP = null;
try
{
if (_qIOCPHandle.Count > 0)
hIOCP = _qIOCPHandle.Dequeue() as IOCPHandle;
}
catch (Exception)
{
}
#endif
// Note:
// Checking for (hIOCP != null) is actually not required.
// But we are getting a null IOCPHandle object from Lock-Free Queue.
// I need to investigate this.
//
if ((empty == false) && (hIOCP != null))
{
if (hIOCP.WaitingOnIOCP == true)
{
hIOCP.SetEvent();
break;
}
else
{
if (hIOCP.OwningThread.ThreadState != ThreadState.Running)
{
// Set the active flag to 2 and decrement the active threads
// so that other waiting threads can process requests
//
int activeTemp = hIOCP._active;
int newActiveState = 2;
if (Interlocked.CompareExchange(ref hIOCP._active,
newActiveState, activeTemp) == activeTemp)
{
DecrementActiveThreads();
}
}
else
{
// This is required because, Thread associated with hIOCP
// may have got null out of ManagedIOCP queue, but still
// not yet reached the QueuIOCPHandle and Wait state.
// Now we had a dispatch and we enqueued the object and
// trying to wake up any waiting threads. If we ignore this
// running thread, this may be the only thread for us and we
// will never be able to service this dispatch untill another
// dispatch comes in.
//
hIOCP.SetEvent();
break;
}
}
}
else
{
// Do we need to throw this exception ???
//
// throw new Exception("No threads avialable to handle the dispatch");
break;
}
}
}
This technique provides the second aspect of native Win32 IOCP's concurrency management that guarantees _atleast_ the maximum number of allowed concurrent threads are _always_ notified/awakened to process queued objects, if the number of threads using the Managed IOCP instance is more than the maximum number of allowed concurrent threads. 5. Points of interestI published part two of this article "Managed I/O Completion Ports - Part 2" that covers Managed IOCP with Lock-Free Queue and Lock-Free ObjectPool, ManagedIOCP based ThreadPool, and a generic Task Framework to be used by Managed IOCP ThreadPool. Here is the link for the article: Managed I/O Completion Ports - Part 2. 5.1. Managed IOCP and MonoManaged IOCP (Sonic.Net assembly, but _not_ demo applications) conforms to core .NET specifications, and can be compiled and used on the Mono .NET runtime. I tested this with Mono 1.1.13.x, and it is working fine on both Windows and Linux platforms (Red Hat Enterprise Linux, RHEL 3). 6. HistoryDate: Apr 17, 2006Fixed an issue related to the usage of Date: Aug 15, 2005Sonic.Net v1.1 - Lock-Free Date: May 09, 2005I fixed a small bug in the Windows demo application (that existed in version 1.0). This bug can allow two threads in the _demo_ application to use the same Date: May 04, 2005Sonic.Net v1.0 (class library hosting 7. Software UsageThis software is provided "as is" with no expressed or implied warranty. I accept no liability for any type of damage or loss that this software may cause. | ||||||||||||||||||||