Multithreading programming learning notes-using concurrent sets (1), multithreading programming learning notes
Introduction to multithreading programming learning notes-async and await (I)
Multi-thread programming learning notes-async and await (2)
Multi-thread programming learning notes-async and await (III)
Programming requires an understanding of basic data structures and algorithms. When programmers select the most appropriate data structure for concurrency, they need to know the algorithm running time and space complexity.
For parallel computing, we need to use appropriate data structures that require scalability to avoid deadlocks and provide thread-safe access .. Net Framework 4.0 introduces the System. Collections. Concurrent namespace, which contains some data structures suitable for Concurrent computing.
Note: Because the concurrent dictionary uses a lock, you do not need to use the following operations: Count, IsEmpty, Keys, Values, CopyTo, and ToArray.
5. BlockingCollection is an advanced encapsulation of the implementation of the IProducerConsumerCollection generic interface. It has many advanced functions to implement pipeline scenarios, that is, when you need to use the results of previous steps. The BlockingCollectione class supports the following functions: field division, internal set capacity adjustment, collection cancellation, and element acquisition from multiple block sets.
Parallel Algorithms may be very complex and cover more or less these parallel sets. Thread security does not have no cost. Compared with System. collections and System. collections. for the classic list, set, and array in the Generic namespace, the concurrent set has a higher overhead. Therefore, the concurrent set should be used only when the set needs to be accessed concurrently from multiple tasks. Using concurrent sets in medium code is meaningless because they increase unnecessary overhead. Below we will use the simplest example to learn these parallel sets.
1. Use ConcurrentDictionary
This example shows a very simple scenario, comparing the performance of using a common dictionary set in a single-threaded environment with the use of a concurrent dictionary.
1. Sample Code of the program, as shown below.
Using System; using System. collections. generic; using System. linq; using System. text; using System. threading. tasks; using System. collections. concurrent; using System. diagnostics; namespace ThreadCollectionDemo {class Program {const string item = "Dict Name"; public static string CurrentItem; static double time1; static void Main (string [] args) {Console. writeLine (string. format ("----- ConcurrentDictionary operation ----"); var concurrentDict = new ConcurrentDictionary <int, string> (); var dict = new Dictionary <int, string> (); var sw = new Stopwatch (); sw. start (); // normal dictionary, 1 million cycles for (int I = 0; I <1000000; I ++) {lock (dict) {dict [I] = string. format ("{0} {1}", item, I) ;}} sw. stop (); Console. writeLine (string. format ("1 million write operations on a common Dictionary set (Dictionary) sharing time: {0} ----", sw. elapsed); time1 = sw. elapsed. totalMilliseconds; sw. restart (); for (int I = 0; I <1000000; I ++) {concurrentDict [I] = string. format ("{0} {1}", item, I);} sw. stop (); Console. writeLine (string. format ("1 million write operations on the parallel dictionary set (ConcurrentDictionary) sharing time: {0}", sw. elapsed); Console. writeLine (string. format ("write operation common dictionary/parallel dictionary = {0}", time1/1.0/sw. elapsed. totalMilliseconds); Console. writeLine (); sw. restart (); for (int I = 0; I <1000000; I ++) {lock (dict) {CurrentItem = dict [I] ;}} sw. stop (); Console. writeLine (string. format ("1 million read operations on a common Dictionary set (Dictionary) sharing time: {0} ----", sw. elapsed); time1 = sw. elapsed. totalMilliseconds; sw. restart (); for (int I = 0; I <1000000; I ++) {CurrentItem = concurrentDict [I];} sw. stop (); Console. writeLine (string. format ("1 million read operations on the parallel dictionary set (ConcurrentDictionary) sharing time: {0} ----", sw. elapsed); Console. writeLine (string. format ("read operation common dictionary/parallel dictionary = {0}", time1/1.0/sw. elapsed. totalMilliseconds); // multi-threaded parallel reading of Data sw. restart (); Task [] process = new Task [4]; for (int I = 0; I <4; I ++) {process [I] = Task. run () => Get (concurrentDict, I);} Task. whenAll (process); sw. stop (); Console. writeLine (string. format ("multi-thread 1 million read operations on the parallel dictionary set (ConcurrentDictionary) sharing time: {0}", sw. elapsed); Console. writeLine (string. format ("read Operations common dictionary/multi-thread read parallel dictionary = {0}", time1/1.0/sw. elapsed. totalMilliseconds); Console. read ();} private static void Get (ConcurrentDictionary <int, string> dict, int index) {for (int I = 0; I <1000000; I ++ = 4) {if (I % 4 = index) {string s = dict [I] ;}}}
2. The running result of a single thread, such.
When the program starts, we create two sets, one of which is the standard dictionary set, and the other is the new concurrent dictionary set. Then, the lock mechanism is used to add elements to the Standard Dictionary and measure the completion time of 1 million times. At the same time, the same scenario is used to measure the performance of ConcurrentDictionary, and finally compare the performance of Obtaining values from both sets.
Through the comparison above, we found that the ConcurrentDictionary write operation is much slower than the general dictionary using the lock, while the read operation is faster. Therefore, ConcurrentDictionary is the best choice for a dictionary that requires a large number of thread-safe read operations.
Finally, when we use four threads to read ConcurrentDictionary at the same time, we will find that this dictionary has better performance.
Add a four-thread read dictionary code to the main method. As follows.
// Multi-thread parallel reading of Data sw. restart (); Task [] process = new Task [4]; for (int I = 0; I <4; I ++) {process [I] = Task. run () => Get (concurrentDict, I);} Task. whenAll (process); sw. stop (); Console. writeLine (string. format ("multi-thread 1 million read operations on the parallel dictionary set (ConcurrentDictionary) sharing time: {0}", sw. elapsed); Console. writeLine (string. format ("read Operations common dictionary/multi-thread read parallel dictionary = {0}", time1/1.0/sw. elapsed. totalMilliseconds); private static void Get (ConcurrentDictionary <int, string> dict, int index) {for (int I = 0; I <1000000; I + = 4) {if (I % 4 = index) {string s = dict [I] ;}}
3. In the case of multiple threads, the program running results, such. It can be seen that the operations of common dictionaries are more than 30 times that of multi-threaded parallel dictionaries. This is the case on my machine. The configurations of each machine are different. Test the configurations on your own.