Recently, I was involved in a Wintellect consulting engagement where a customer had some class library code that was created years ago. The code in this class library was not designed to work well in a multithreaded environment. Specifically, this means that if two or more threads called into the class library code at the same time, the data got corrupted and the results could not be trusted. Non-concurrent-ready code is easy to obtain with frequent use of mutable static fields. But, there are other common coding practices which can result in code that is not safe with multiple threads passing through it at the same time. This customer wanted to increase the performance of their code and the best way to accomplish this is to have multiple threads processing their own data independently of each other. But, again, the class library code base would produce errant results if we did this. To demonstrate the problem, imagine this very simple static class which is indicative of the non-thread safe class library that I'm referring to: internal static class NonConcurrentAlgorithm
{
NonConcurrentAlgorithm.Add("Item 2");
Thread.Sleep(1000); // To demonstrate the problem consistently
foreach (var i in NonConcurrentAlgorithm.GetItems())
Console.WriteLine(i);When this code runs, the console could show:"Item 2" by itself
"Item 2" followed by "Item 1"
"Item 1" followed by "Item 2" Clearly, this is not desireable and the problem all stems from the threads sharing the one List<String> object. In fact, the List<T> class is not itself thread-safe and so it is even possible that having multiple threads accessing it simultaneously could result in items disappearing or a single item being inserted multiple times, or worse. It depends on how the List<T> class is internally implemented which is not documented and is subject to change. Now, one way to improve performance for the customer would be for Wintellect to take the non-concurrent code base and make it thread safe. This is the best thing to do; however, the code base was very large, the changes to the code base would have been substantial and a lot of testing would have had to be done. The customer was not too excited by this recommendation. So, what we did instead was use the CLR's AppDomain feature. This works out great because an AppDomain's state is completely isolated from all other AppDomains. To communicate across AppDomains, you must create a class derived from MarshalByRefObject. So, I created a NonConcurrentAlgorithmProxy class that wraps the methods of the static class. The NonConcurrentAlgorithmProxy class looks like this: internal sealed class NonConcurrentAlgorithmProxy : MarshalByRefObject
{
private static List<String> s_items = new List<String>();}If we have two threads using this class simultaneously, the results are unpredictable. For example, let's say we have this code: ThreadPool.QueueUserWorkItem(o => NonConcurrentAlgorithm.Add("Item 1"), null);
public static void Add(String item) { s_items.Add(item); }
public static String[] GetItems() { return s_items.ToArray(); }
NonConcurrentAlgorithm.Add("Item 2");
Thread.Sleep(1000); // To demonstrate the problem consistently
foreach (var i in NonConcurrentAlgorithm.GetItems())
Console.WriteLine(i);When this code runs, the console could show:"Item 2" by itself
"Item 2" followed by "Item 1"
"Item 1" followed by "Item 2" Clearly, this is not desireable and the problem all stems from the threads sharing the one List<String> object. In fact, the List<T> class is not itself thread-safe and so it is even possible that having multiple threads accessing it simultaneously could result in items disappearing or a single item being inserted multiple times, or worse. It depends on how the List<T> class is internally implemented which is not documented and is subject to change. Now, one way to improve performance for the customer would be for Wintellect to take the non-concurrent code base and make it thread safe. This is the best thing to do; however, the code base was very large, the changes to the code base would have been substantial and a lot of testing would have had to be done. The customer was not too excited by this recommendation. So, what we did instead was use the CLR's AppDomain feature. This works out great because an AppDomain's state is completely isolated from all other AppDomains. To communicate across AppDomains, you must create a class derived from MarshalByRefObject. So, I created a NonConcurrentAlgorithmProxy class that wraps the methods of the static class. The NonConcurrentAlgorithmProxy class looks like this: internal sealed class NonConcurrentAlgorithmProxy : MarshalByRefObject
{
public void Add(String item) { NonConcurrentAlgorithm.Add(item); }
public String[] GetItems() { return NonConcurrentAlgorithm.GetItems(); }
}Read more: JEFFREY RICHTER'S BLOG
public void Add(String item) { NonConcurrentAlgorithm.Add(item); }
public String[] GetItems() { return NonConcurrentAlgorithm.GetItems(); }
}Read more: JEFFREY RICHTER'S BLOG
0 comments:
Post a Comment