Directory
- First, preface
- Second, Concurrentbag class
- Three, concurrentbag thread safety implementation principle
- 1. Private fields of Concurrentbag
- 2. Trehadlocallist Class for data storage
- 3. Concurrentbag implementation of new elements
- 4. Concurrentbag How to implement the iterator pattern
- Iv. Summary
- The author level is limited, if the mistake welcome everybody criticism correct!
First, preface
The author recently in a project, in order to improve throughput, the use of Message Queuing, the middle of the implementation of production and consumption patterns , in the production of consumer models need to have a set to store producers of goods produced by the author used the most common List<T>
collection type.
Because there are many producer threads, and there are many consumer threads, the problem of thread synchronization inevitably arises. The author is the use of lock
keywords, thread synchronization, but performance is not particularly ideal, and then a netizen said can be used SynchronizedList<T>
instead of using to List<T>
achieve the purpose of thread safety. So the author replaced SynchronizedList<T>
, but found that the performance is still bad, and then looked at SynchronizedList<T>
the source code, found that it is simple in the List<T>
provision of the API based on the addition lock
, so the performance is basically the same way as the author.
Finally, the author found a solution, using ConcurrentBag<T>
classes to achieve, performance has a great change, so I looked at ConcurrentBag<T>
the source code, the implementation is very subtle, hereby recorded in this.
Second, Concurrentbag class
ConcurrentBag<T>
Implementation IProducerConsumerCollection<T>
of the interface, the interface is mainly used in the producer consumer mode, it can be seen that the class is basically for the production of consumer model customization. Then the general class is implemented and IReadOnlyCollection<T>
the class is implemented to implement the class IEnumerable<T>、IEnumerable、 ICollection
.
ConcurrentBag<T>
There are not so many methods available externally List<T>
, but there are also Enumerable
implementations of extension methods. The methods provided by the class itself are as follows.
name |
Description |
Add |
Adds an object to the Concurrentbag . |
CopyTo |
Copies the Concurrentbag element to an existing one-dimensional array, starting at the specified array index. |
Equals (Object) |
Determines whether the specified object is equal to the current object. (Inherit from Object.) ) |
Finalize |
Allows an object to attempt to free resources and perform other cleanup operations before garbage collection is reclaimed. (Inherit from Object.) ) |
GetEnumerator |
Returns an enumerator that iterates through the concurrentbag. |
GetHashCode |
Serves as a hash function for a particular type. (Inherit from Object.) ) |
GetType |
Gets the Type of the current instance. (Inherit from Object.) ) |
MemberwiseClone |
Creates a shallow copy of the current Object. (Inherit from Object.) ) |
ToArray |
Copies the Concurrentbag element to the new array. |
Tostring |
Returns a String that represents the current object. (Inherit from Object.) ) |
Trypeek |
An attempt was attempted to return an object from Concurrentbag without removing the object. |
TryTake |
Attempts to remove and return an object from the Concurrentbag. |
Third, concurrentbag thread safety implementation principle 1. Private fields of Concurrentbag
ConcurrentBag
The thread-safe implementation is mainly through the structure of its data storage and fine-grained locks.
public class ConcurrentBag<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T> { // ThreadLocalList对象包含每个线程的数据 ThreadLocal<ThreadLocalList> m_locals; // 这个头指针和尾指针指向中的第一个和最后一个本地列表,这些本地列表分散在不同线程中 // 允许在线程局部对象上枚举 volatile ThreadLocalList m_headList, m_tailList; // 这个标志是告知操作线程必须同步操作 // 在GlobalListsLock 锁中 设置 bool m_needSync;}
It is preferred that we look at the private fields that it declares, and it is important to note that the collection's data is stored in ThreadLocal
the thread's local storage. That is, each thread that accesses it maintains a list of its own collection data, which may be stored in a different thread's local storage , so if the thread accesses its own locally stored object, then there is no problem, which is the first layer of thread safety. Store data locally using threads .
You can then see ThreadLocalList m_headList, m_tailList;
that this is the head and tail pointers that hold the local list object, and through these two pointers, we can iterate through all the local lists. It uses volatile
adornments, does not allow threads to be cached locally, and reads and writes for each thread directly on shared memory, ensuring that variables are always consistent. Any thread that reads and writes at any time is the latest value. for volatile
modifiers, thank me for being the siege Lion pointed out the description error.
Finally, a flag is defined that tells the operating thread to synchronize, which is a fine-grained lock, because thread synchronization is required only if several conditions are met.
2. Trehadlocallist Class for data storage
Let's take ThreadLocalList
a look at the construction of the class, which is where the data is actually stored. In fact, it is the use of a doubly linked list of this structure for data storage.
[serializable]//constructs the node of the doubly linked list internal class node{public node (T value) {m_value = value; } public readonly T m_value; Public Node M_next; Public Node M_prev;} <summary>///collection operation type//</summary>internal enum listoperation{None, Add, take};///<summary> //////</summary>internal class threadlocallist{//The head node of the doubly linked list is null so that the list is empty internal volatile Node m_ Head The tail node of the doubly linked list is private volatile node m_tail; Defines the type of current operation on the list//corresponding to the previous listoperation internal volatile int m_currentop; The count of this list element is private int m_count; The stealing count//This is not particularly understood as if a node is deleted in the local list after a count internal int m_stealcount; The next list may internal volatile threadlocallist m_nextlist in other threads; Sets whether the lock has been internal bool M_locktaken; The owner thread for this list internal thread m_ownerthread; The version of the list is the underlying internal volatile int m_version only if the list changes from null to non-empty statistics; <summary>//ThreadLocalList constructor///</summary>//<param name= "Ownerthread" > Threads with this collection </param> internal Threadlocallis T (Thread ownerthread) {m_ownerthread = Ownerthread; }///<summary>///Add a new item to the list header////</summary>//<param name= "item" >the Item to ADD.&L t;/param>//<param name= "Updatecount" > whether to update count .</param> internal void Add (T item, BOOL Updatecount) {checked {m_count++; } node node = new node (item); if (M_head = = null) {Debug.Assert (m_tail = = null); m_head = node; m_tail = node; m_version++; Because it is initialized, the empty state is changed to a non-empty state} else {//Insert new element into the list Node.m_next = M_head using the head interpolation method; M_head.m_prev = node; m_head = node; } if (Updatecount)//Update count to avoid this add synchronization when overflow {m_count = M_count-m_stealcount; M_stealcount = 0; } }///<summary>///Remove an item from the head of the list///</summary>//<param name= "result" >the removed it em</param> internal void Remove (out T result) {//Doubly linked list the process of deleting head node data debug.assert (M_head! = null); Node head = M_head; M_head = M_head.m_next; if (m_head! = null) {M_head.m_prev = null; } else {m_tail = null; } m_count--; result = Head.m_value; }///<summary>///return elements of the list header///</summary>//<param name= "result" >the peeked Item</pa ram>//<returns>true if succeeded, false otherwise</returns> internal bool Peek (out T result) { Node head = M_head; if (head! = null) {result = Head.m_value; return true; } result = Default (T); return false; }///<summary>///Get an item from the end of the list////</summary>//<param NAme= "Result" >the removed item</param>//<param name= "Remove" >remove or peek flag</param> inte rnal void Steal (out T result, bool remove) {Node tail = m_tail; Debug.Assert (tail! = NULL); if (remove)//take operation {m_tail = M_tail.m_prev; if (m_tail! = null) {m_tail.m_next = null; } else {m_head = null; }//Increment the steal Count m_stealcount++; } result = Tail.m_value; }///<summary>////To get a total list count, which is not thread safe and may provide incorrect count if it is called at the same time///</summary> Internal int count {get {return m_count-m_stealcount; } }}
From the above code, we can more verify that the previous point of view, is to ConcurentBag<T>
store data in a thread, using a doubly linked list , ThreadLocalList
to achieve a set of linked list additions and deletions to change the method.
3. Concurrentbag implementation of new elements
Let's take a look at ConcurentBag<T>
how to add a new element.
<summary>///attempts to get a non-master list, which means that the thread has been paused or terminated, but some of the data in the collection is stored there///This is a way to avoid memory leaks///</summary>///< Returns></returns>private threadlocallist getunownedlist () {//Must hold global lock Contract.assert at this time (Monitor.IsEntered (Globallistslock)); Start enumerating from the list of threads to find those threads that have been closed//return the list object where it is threadlocallist currentlist = m_headlist; while (currentlist! = null) {if (currentList.m_ownerThread.ThreadState = = System.Threading.ThreadState.Stopped) {currentlist.m_ownerthread = Thread.CurrentThread;//The caller should acquire a lock to make this Li NE thread safe return currentlist; } currentlist = Currentlist.m_nextlist; } return null; <summary>///Local Help method, retrieves thread-thread-local list by thread object//</summary>///<param name= "Forcecreate" > If the list does not exist, Then create a new list </param>///<returns>the Local list object</returns>private threadlocallist GetThreadList ( BOOL forcecreate) {threadlocallist list = M_locals. Value;if (list! = null) {return list; } else if (forcecreate) {//Get m_taillist Lock (globallistslock) for update operation {//If header column The table equals NULL, then the description does not have an element in the collection//directly creates a new if (m_headlist = = null) {list = new Threadl Ocallist (Thread.CurrentThread); M_headlist = list; M_taillist = list; The data in the else {//concurrentbag is distributed in the local area of each thread in the form of a doubly linked list//by using the following method to find the Some threads that store data but have been stopped//and then hand over the data of the stopped thread to the current thread management list = Getunownedlist (); If not, create a new list and then update the position of the tail pointer if (list = = null) {list = new Threadlocalli St (Thread.CurrentThread); M_taillist.m_nextlist = list; M_taillist = list; }} m_locals. Value = list; }} else {return null; } Debug.asseRT (List! = null); return list;} <summary>///Adds An object to the <see cref= "Concurrentbag{t}"/>.///</summary>///<param name= "Item" >the object to being added to the///<see cref= "Concurrentbag{t}"/>. The value can be a null reference///(Nothing in Visual Basic) for reference types.</param>public void Add (T item) { Gets the local list of the thread, and if this thread does not exist, creates a new list (the first call to add) threadlocallist list = Getthreadlist (true); The actual data add operation executes addinternal (list, item) in Addinternal;} <summary>///</summary>///<param name= "list" ></param>///<param name= "Item" ></ param>private void addinternal (threadlocallist list, T item) {bool LockTaken = false; try {#pragma warning disable 0420 interlocked.exchange (ref list.m_currentop, (int) listoperation.add); #pragma warning Restore 0420//Sync case://If the list count is less than two because it is a relationship of the doubly linked list in order to avoid collisions with any stealing threads must acquire the lock//if Set M_needsync, which means that there is a thread that needs to freeze the package and must getLock if (list. Count < 2 | | M_needsync) {//resets it to None to avoid deadlock with the stealing thread list.m_currentop = (int) listoperation.none; Locks the current object Monitor.Enter (list, ref LockTaken); }//Call the Threadlocallist.add method to add data to the doubly linked list//If it is already locked then thread safety can update the count Count list. ADD (item, LockTaken); } finally {list.m_currentop = (int) listoperation.none; if (LockTaken) {monitor.exit (list); } }}
From the above code, we can clearly know how the Add()
method is running, the key is the method GetThreadList()
, through this method can get the current thread of the data storage list object, if there is no data storage list, it will automatically create or GetUnownedList()
through method to look for threads that are stopped but also store a list of data, and then return the list of data to the current thread, preventing a memory leak.
In the process of adding data, a fine-grained synchronization lock is implemented, lock
so the performance is very high. Deletions and other actions are similar to additions, and this article does not repeat.
4. Concurrentbag How to implement the iterator pattern
After reading the code above, I was curious ConcurrentBag<T>
about how it IEnumerator
would be implemented to achieve iterative access because ConcurrentBag<T>
it was stored in different threads ThreadLocalList
, so the process would be more complex when implementing the iterator pattern.
After reviewing the source code, we found that in ConcurrentBag<T>
order to implement the iterator pattern, the data that was divided into different threads was stored in a List<T>
collection, and then the iterator to that replica was returned. So every time you access an iterator, it creates a new List<T>
copy, which wastes a certain amount of storage space, but is logically simpler.
<summary>///Local helper method frees all local list locks//</summary>private void Releasealllocks () {//This method is used to release all local locks after thread synchronization is performed Frees the lock threadlocallist currentlist = M_headlist by traversing the Threadlocallist object stored in each thread; while (currentlist! = null) {if (Currentlist.m_locktaken) {Currentlist.m_locktaken = false; Monitor.Exit (currentlist); } currentlist = Currentlist.m_nextlist; }}///<summary>///Local helper method to thaw packets from frozen state///</summary>///<param name= "LockTaken" >the lock taken result From the Freeze method</param>private void Unfreezebag (bool lockTaken) {//First release the lock for local variables in each thread//and then release the global lock Re Leasealllocks (); M_needsync = false; if (LockTaken) {monitor.exit (globallistslock); }}///<summary>///Local helper function waits for all unsynchronized operations//</summary>private void Waitalloperations () {Contract.assert ( Monitor.isentered (Globallistslock)); Threadlocallist currentlist = m_headlist; Spin wait for other operations to complete while(currentlist! = null) {if (Currentlist.m_currentop! = (int) listoperation.none) {SpinWait spinner = new SpinWait (); When other threads are operating, the cuurentop is set to the enumeration while being manipulated while (currentlist.m_currentop! = (int) listoperation.none) {Spinner. SpinOnce (); }} currentlist = Currentlist.m_nextlist; }}///<summary>///Local helper method gets all Local list locks//</summary>private void Acquirealllocks () {Contract.assert ( Monitor.isentered (Globallistslock)); BOOL LockTaken = false; Threadlocallist currentlist = m_headlist; Iterate through the threadlocallist of each thread and get the corresponding threadlocallist lock while (currentlist! = null) {///try/Last bllock to avoid getting locks and settings taken The flags between the threads port try {monitor.enter (currentlist, ref lockTaken); } finally {if (lockTaken) {Currentlist.m_locktaken = true; LockTaken = false; }} CUrrentlist = currentlist.m_nextlist; }}///<summary>///Local Helper method to freeze all bag operations, it///1-acquire the global lock to prevent any Other thread to freeze the bag, and also new new thread can is added///to the dictionary///2-then acquire all local Li STS locks to prevent steal and synchronized operations///3-wait for all un-synchronized operations to be done///</SU mmary>///<param name= "LockTaken" >retrieve the lock taken result for the global lock, to is passed to unfreeze me thod</param>private void Freezebag (ref bool LockTaken) {Contract.assert (! Monitor.isentered (Globallistslock)); Global locking safely prevents multithreaded calls from counting and corrupting M_needsync Monitor.Enter (Globallistslock, ref lockTaken); This forces the synchronization of any future Add/execute operations M_needsync = true; Gets the lock Acquirealllocks () for all lists; Wait for all operations to complete waitalloperations ();} The <summary>///Local helper function returns the package items in the list, which are used primarily by CopyTo and ToArray. This is not thread safe and should be called freeze/thaw bag block///This method is private only after using Freeze/unfreeze is safe///</summary>///<returns>list the contains the bag items</returns>private list<t> ToList () {Contract.asse RT (Monitor.isentered (Globallistslock)); Create a new list list<t> list = new list<t> (); Threadlocallist currentlist = m_headlist; Traversing the threadlocallist in each thread adds the data of node inside to the list while (currentlist! = null) {Node CurrentNode = currentlist. M_head; while (CurrentNode! = null) {list. ADD (Currentnode.m_value); CurrentNode = Currentnode.m_next; } currentlist = Currentlist.m_nextlist; } return list;} <summary>///Returns An enumerator that iterates through the <see///cref= "Concurrentbag{t}"/>.///</s ummary>///<returns>an Enumerator for the contents of the <see///cref= "Concurrentbag{t}"/>.</returns >///<remarks>///The enumeration represents a moment-in-time snapshot of the contents///of the bag. It does not reflect any updates to the COllection after//<see cref= "GetEnumerator"/> was called. The enumerator is safe to use///concurrently with reads from and writes to the bag.///</remarks>public IEnumerator <T> GetEnumerator () {//short path if the bag is empty if (m_headlist = = null) return new list<t> (). GetEnumerator (); Empty list bool LockTaken = FALSE; try {//freezes the entire Concurrentbag collection freezebag (ref LockTaken) first; Then ToList gets the list of IEnumerator return ToList (). GetEnumerator (); } finally {unfreezebag (lockTaken); }}
As the above code knows, in order to get the iterator object, a total of three steps are taken to perform the main operation.
- Use
FreezeBag()
the method to freeze the entire ConcurrentBag<T>
collection. Because a copy of the collection needs to be generated List<T>
, no other thread can change the corrupted data during the generation of the replica.
- A
ConcurrrentBag<T>
copy is generated List<T>
. Because ConcurrentBag<T>
of the special way to store data, it is difficult to implement the iterator pattern directly, and the best way to take into account thread safety and logic is to generate a copy.
- After you do this, you can use
UnfreezeBag()
the method to unfreeze the entire collection.
So FreezeBag()
how does the method freeze the entire collection? is also divided into three steps to go.
- The global lock is first obtained by
Monitor.Enter(GlobalListsLock, ref lockTaken);
such a statement that other threads cannot freeze the collection.
- The lock in all threads
ThreadLocalList
is then fetched, and the ' Acquirealllocks () method is used to traverse the fetch. This way, other threads cannot manipulate it to corrupt data.
- The wait has entered the end of the action flow thread, implemented by means of a method that
WaitAllOperations()
iterates through ThreadLocalList
the properties of each object m_currentOp
to ensure that it is all in None
action.
After completing the above process, it is true to freeze the entire ConcurrentBag<T>
set, to thaw the words are similar. Don't dwell on it here.
Iv. Summary
Here is a diagram that describes ConcurrentBag<T>
how data is stored. Thread-local storage is implemented through each thread, and ThreadLocal
each thread has such a structure that does not interfere with each other. Then each thread m_headList
always points to ConcurrentBag<T>
the first list, pointing to the m_tailList
last list. m_locals
a single linked list is formed by connecting the lists to the list m_nextList
.
The data is stored in each thread m_locals
, and the Node
class forms a doubly linked list.
PS: Be aware of m_tailList
and m_headList
not store in ThreadLocal
, but all threads share one copy.
The above is the implementation of the ConcurrentBag<T>
class, the author of some records and analysis.
The author level is limited, if the mistake welcome everybody criticism correct!
Attach ConcurrentBag<T>
Source address: poke a poke