Self-thinking multithreading (2) and Self-thinking Multithreading

Source: Internet
Author: User

Self-thinking multithreading (2) and Self-thinking Multithreading

As you know in the previous article, scheduling between threads is uncontrollable. When we write multi-threaded programs, the thread is out of order. Otherwise, thread security problems may occur.

Why? Because when multiple threads are running in the program, you cannot determine which thread is being executed. A may execute A line of code. In this case, switch to B to execute A line of code, then switch back to A and execute A line of code. This is all possible. Don't think that my code is short, so one or two lines won't need to be locked, and the multi-thread program must be rigorous.

How can we ensure rigor?

That is, when your program is using shared resources, when multiple threads may call the same variable or access the same memory, ensure linear execution of this Code. For example, I have the following code:

Public class DbActionQueue: IDisposable {public Queue <Action> _ transQueue; private Thread _ thread; private bool _ isDispose = false; private static readonly object _ syncObject = new object (); private readonly object _ syncQueueObject = new object (); private static DbActionQueue _ instance; public static DbActionQueue Instance {get {if (_ instance = null) {lock (_ syncObject) {if (_ instance = nul L) {_ instance = new DbActionQueue () ;}} return _ instance ;}} private DbActionQueue () {if (_ transQueue = null) {_ transQueue = new Queue <Action> ();} if (_ thread = null) {_ thread = new Thread (Thread_Work) {IsBackground = true};} _ thread. start ();} public void Push (Action action) {if (_ transQueue = null) throw new ArgumentNullException ("dbActionQueue is not init"); lock (_ syncQueueObje Ct) {_ transQueue. Enqueue (action) ;}} public void Thread_Work () {while (! _ IsDispose) {Action [] items = null; if (_ transQueue! = Null & _ transQueue. count> 0) {lock (_ syncQueueObject) {items = new Action [_ transQueue. count]; _ transQueue. copyTo (items, 0); _ transQueue. clear () ;}} if (items! = Null & items. length> 0) {foreach (var item in items) {try {item. invoke ();} catch (Exception ex) {LogHelper. write (string. format ("DbActionQueue error. | Exception. stackTrace: {0} ", ex. stackTrace), ex) ;}} Thread. sleep (1) ;}} public void Dispose () {_ isDispose = true; _ thread. join ();}}View Code

I used the lock when I was in Enqueue and when I was Clear. here we need to say that when you want to lock the block logic, you must lock the same object. Otherwise, it makes no sense. Why is there a problem if I don't lock it?

Without locking, the first problem is data loss. When I run the copyto code line in one thread, one thread executes the Enqueue. At this time, the current thread will continue to run Clear, and the Enqueue data will be cleared, which is equivalent to losing a piece of data.

If the code is slightly changed:

While (! _ IsDispose) {Action item = null; lock (_ syncObject) {if (_ transQueue! = Null & _ transQueue. Count> 0) {item = _ transQueue. Dequeue () ;}} item. Invoke ();}View Code

We will find that the logic Execution Code. the invoke () is placed outside the lock. As mentioned in the previous blog, the lock will cause a series of problems. If I retrieve a single entry, can I leave it unlocked?

No, because when a queue is running Dequeue again in Enqueue, unexpected bugs such as dirty reads and Phantom reads will appear in this queue. However, you can solve this problem by changing to ConcurrentQueue. However, if you perform batch fetch, changing to ConcurrentQueue will still cause the aforementioned data loss problem, because thread scheduling is uncontrollable, there is no special literature on whether ConcurrentQueue thread security uses atomic locks or spin locks. We will not discuss them here. Here is another thing to say: Batch fetch is to avoid frequent locks. You can control the number of batch fetch entries at a time. I will finish it at a time, you can control 10, 20, 50, and so on at a time.

We will find that the premise of uncontrollable Thread Scheduling makes it difficult to control exceptions when we need to collaborate among multiple threads. Therefore, when designing a program, avoid multi-threaded collaboration as much as possible. If this happens, do not take it for granted that your code will be executed according to your own understanding. Here is an example:

The Code roughly means that there is a network module. After receiving a message from the client, it is allocated to the queue of a thread. After the thread completes processing, it is thrown to the sending thread. The core code is as follows:

Protected virtual void extends ecallback (string ip, int port, string url, bool isLargePack, IntPtr streamHandle, long streamSize, IntPtr bodyData, int bodySize, IntPtr responseHandle) {// initialize a thread wait event (signal light) AutoResetEvent autoEvent = null; // when asynchronous processing is enabled (because this module supports synchronous and asynchronous) if (! This. _ isSync) {autoEvent = new AutoResetEvent (false);} // read data from streamHandler var data = Read2Byte (streamHandle, bodyData, streamSize, bodySize, isLargePack ); // convert to the internal protocol data (Bson) var obj = BsonHelper. toObject <Communication> (data); // an Action <Communication, IntPtr, object> if (already ed! = Null) {Received. Invoke (obj, responseHandle, autoEvent);} // blocking until the signal if (autoEvent! = Null) {autoEvent. WaitOne (this. _ timeOut );}}View Code

Receive. Invoke where Receive is an Action, the Code is as follows:

Public void InvokeCommand (Communication obj, IntPtr connect, object e) {// if (obj = null | string. isNullOrEmpty (obj. command) {obj = new Communication {Command = "ErrorCommand", Body = new Newtonsoft. json. linq. JObject ()}; obj. body ["Token"] = Guid. newGuid (). toString () ;}var unit = new InternelUnit {Event = e, Packet = obj, Connection = connect}; // whether to synchronize if (this. _ isSync) {this. requestCallBack (unit);} else {// put into the business processing queue RequestQueueManage. instance. push (unit );}}View Code

The two codes mean that after the network module receives the message, it is thrown to the thread queue. Because of the lifecycle control, the RequestHandler handle is valid only in the method body. If the method body ends, the handle is released. So we have it. After pushing it to the thread queue, we have done a WaitOne processing of the signal. The Code is as follows:

Public void ResponseCallBack (InternelUnit) {// indicates whether the packet loss pool is required. if (unit. isInLastPackPool) {Core. lostPacketPool. lostPacketPool. instance. push (ref unit);} // convert to byte [] var repBson = BsonHelper by protocol. toBson (unit. packet); // whether to enable encryption if (this. _ isEncrypt) {repBson = EncryptHelper. decrypt (repBson, repBson. length);} // send Network. networkHelper. send (unit. connection, repBson, unit. id); // whether to enable asynchronous if (! _ IsSync) {// release signal (unit. Event as System. Threading. AutoResetEvent). Set ();}}View Code

This entire code segment will not be faulty in most cases, but as we have just mentioned, thread scheduling is uncontrollable, so we cannot guarantee it in Receive. after Invoke (), the code continues to run down and WaitOne () is executed. If. after Invoke, the program switches to and the business processing thread may appear. First Set () is executed to release the signal, and then WaitOne () is executed, a deadlock will occur, but fortunately, we have time-out control, and there will be no absolute deadlock (but it is almost the same ).

Therefore, writing this program is not rigorous, and there will be a lot of inexplicable timeout. When the program really needs multi-thread collaboration, Use callback as much as possible to handle it, and control the lifecycle to avoid unreleased resources as much as possible.

Another common example of voting is as follows:

// Obtain the object var article = CacheHelper from the cache. get (articleid); + 1article for thumb ups. up ++ writes back to the cache. Because of the reference technical relationship, this step can be omitted if the cache is controlled within your program (such as Dictionary. // CacheHelper. Set (articleid, article );View Code

A very simple counter code, but when multiple users like it at the same time, the program may add data errors (the reason is not repeated ). So we have the intention to add lock. The Code is as follows:

Lock (object) {voting counter + 1}

Note that, if your skills are insufficient, do not lock (this) as much as possible, because this indicates the current instance, multiple instances may exist in multiple threads, so the lock is not the same object.

At this time, your code seems to be okay, but if your program is deployed on multiple machines, the data error will still occur, right. Because the lock on the two machines is not the same object, you may need to use DB or introduce a third-party middleware (such as redis ), you need to have a place as a unique central control to ensure data consistency. Another way is to modulo the articleid and let the same article like it, to the same machine.

Similarly, this is also true when we are processing data from DB to cache. For example, we have the following code,

var list = CacheHelper.Get(key);if(list == null){    list = GetListFromDB(xxx);}return list;

The problem with this piece of code is that when the GetListFromDB () data changes, the list obtained by multiple machines may be different. You may need to perform some timed synchronization. If multiple threads read data all the time, the problem occurs when multiple threads fetch data from the database at the same time. This is not what we want to see, so we add Lock.

var list = CacheHelper.Get(key);if(list == null){    lock(object){          list = CacheHelper.Get(key);          if(list == null){               list = GetListFromDB(xxx);          }    }}return list;

Why is there a dual judgment? Because when you lock the data, the previous thread may have read the data, so that when multiple threads run here, the cause has been determined, as a result, multiple threads still fetch data from the database. Since data extraction from the database is slow, there will still be a loop like what we mentioned in the previous article, which involves continuous thread scheduling, locking, and switching. So try to use lock with caution.

Thread security is mainly caused by uncontrollable thread scheduling. We need to make sure that we can process shared resources in blocks and execute them linearly.

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.