I believe there are many students in the face of multithreaded code will be afraid, think multithreaded code is like a monster that is difficult to tame, you can not overcome the monster it will devour you in turn.

It’s an exaggeration. In short, multithreaded programs sometimes look like a mud-hole that you can’t get into or out of.

But why? Why is multithreaded code so hard to write correctly?

Think at the root

There’s essentially a word that you don’t quite understand, and that word is something called thread safe.

If you don’t understand thread safety, no amount of solution is going to help you.

Let’s take a look at what thread safety is and how it can be done.

When these questions are answered, the monster of multithreading will naturally become a tame kitten.

But this picture has nothing to do with kittens!

What the hell do you care

In life, we often say a word verbally is “it’s your ass business”, we think about it, why our ass business is none of others?

The reason is simple. It’s my personal business! My clothes, my computer, my phone, my car, my house, and my private pool (which I don’t have, but don’t want to imagine) I can do whatever I want with them. What belongs to me and to me is nobody else’s business, even if it’s none of theirs.

We eat what we want in our own house and go to the bathroom when we want! Because these are all private to me and only for my own use.

So when do you cross paths with other people?

The answer is public.

In public places you can’t go wherever you want as if you were at home. You can go to the bathroom whenever you want. Why? The reason is simple, because restaurants and bathrooms in public places do not belong to your family. They are public resources that everyone can use.

If you want to go to a restaurant or go to a public restroom, you have to follow the rule. The rule is to queue up, and only after the previous person has used up the public resources, the next person can use them, and not at the same time. If you want to use them, you have to queue up and wait.

The above paragraph is simple enough.

If you understand this, taming the little monster of multithreading is no problem.

Maintain order in public places

If you think of yourself as a thread, then using private resources in your own home is thread-safe simply because you can mess with your own stuff (resources) without disturbing anyone else.

But it is different to the sea in public places, the use of public resources in a public place, then you can’t be like in their own home think how to use how to use think when use it when using, must have the corresponding rules in public places, the rule is usually lined up, only in this way the public order will not be destroyed, Thread safety is achieved when threads use shared resources in an order that does not interfere with other threads.

So we can see that there are two cases:

  • Thread-private resources, no thread-safety issues

  • Sharing resources and using shared resources in some order between threads can also achieve thread safety.

This article has revolved around these two core points, and now we can talk about thread safety in programming in a formal way.

What is thread safety

Even though unsafe, a piece of code is thread-safe if, and only if, that code is called multiple times in multiple threads at the same time, it gives the right result. Otherwise, it’s not thread-safe code.

The results of non-thread-safe code are determined by the roll of a die.

The definition of thread safety is simple. It means that your code should run correctly whether it is executed in a single thread or in multiple threads. Such code should not have multithreading problems, such as the following code:

int func() {
  int a = 1;
  int b = 1;
  return a + b;
}
Copy the code

A piece of code that returns 2 no matter how many threads you call at once, how you call it, or when you call it, is thread-safe.

So how do we write thread-safe code? To answer this question, we need to know when our code is using private resources at home and when it is using public resources. That is to say, you need to identify the private and shared resources of the thread. This is the core of solving the thread safety problem.

Thread private resources

The essence of thread running is actually the execution of function, the execution of function always has a source, this source is the so-called entry function, CPU starts from the entry function to form a flow of execution, but we artificially give a name to the execution flow, this name is called thread.

Since thread execution is essentially function execution, where is function runtime information stored?

The answer is the stack area. Every thread has a private stack area, so local variables allocated on the stack are private to the thread, and no matter how we use these local variables, it doesn’t matter to any other thread.

The thread’s private stack area is the thread’s home.

Data is shared between threads

In addition to the areas mentioned in the previous section, the remaining areas are public places, which include:

  • The heap is used to allocate memory dynamically. We use MALloc or new in C/C++ to allocate memory on the heap

  • Global area, where global variables are stored

  • Files, we know that threads are files opened by shared processes

Keep in mind that these two areas cannot be modified, meaning that they are read only, so there is no problem with multiple threads using them.

In the heap, data, and files we mentioned earlier, these are resources that can be shared by all threads. These are the public places where threads can’t run.

Threads must use these shared resources in order, and the core of this order is that the use of shared resources does not interfere with other threads. Whether you use locks or semaphores, the purpose is to maintain order in the public place.

Now that you know what is private and what is shared between threads, it’s easy.

It is worth noting that all the problems about thread safety are dealt with around thread private data and thread shared data, and the main contradiction between thread private resources and shared resources is also seized to solve the core of thread safety problems.

Let’s look at how to implement thread-safety in various situations, again using C/C++ code as an example, but the methods described here can be used in any language, and rest assured that the code is simple enough.

Use only thread-private resources

Let’s look at this code:

int func() {
  int a = 1;
  int b = 1;
  return a + b;
}
Copy the code

This code in the mentioned before, no matter how you in how many threads call when the call, func function will determine the return of 2, the function is not dependent on any global variables, do not rely on any function parameters, and use local variables are thread private resources, such code is also called stateless function, stateless, It is obvious that such code is thread-safe.

Such code please rest assured that bold use in multithreading, there will be no problem.

Some of you might say, well, what if we still use thread private resources, but pass in function parameters?

Thread private resource + function argument

Is such code thread-safe? Think about the problem for yourself. A: It depends. Depending on what?

1. Pass parameters by value

If you pass parameters as values, then there is no problem, the code is still thread-safe:

int func(int num) {
  num++;
  return num;
}
Copy the code

This code will correctly return the value of the argument plus one no matter how many threads it’s called or when it’s called. The reason is simple: the parameters passed in by value are thread-private resources.

2. Pass parameters by reference

But if the argument is passed by reference, the situation is different:

int func(int* num) {
  ++(*num);
  return *num;
}
Copy the code

If the thread calling this function passes in a thread-private resource as an argument, the function is still thread-safe and returns the correct value of the argument incremented by one.

But if the argument passed is a global variable, it looks like this:

int global_num = 1; int func(int* num) { ++(*num); return *num; } // thread1 void thread1() {func(&global_num); } // thread 2 void thread1() {func(&global_num); }Copy the code

The func function is no longer thread-safe code because the passed argument points to the global variable, which is a resource shared by all threads. In this case, the increment of the global variable must impose some order, such as locking, without changing the way the global variable is used.

Some of you might say if I pass in a pointer that’s not a reference to a global variable, isn’t that a problem?

B: It depends.

Even if the parameter we pass is malloc or new from the heap, there may still be a problem. Why?

The answer is simple because the resources on the heap are also shared by all threads.

If two threads call a func function with a pointer (reference) to a variable on the same heap, the variable becomes a shared resource between the two threads, in which case the func function is still not thread-safe.

Improvement is also very simple, that is, each thread calls func function into a single address belongs to the thread resources, so each thread will not interfere with each other, therefore, a great principle is to write thread-safe code can use thread to private resources using the private resources, between threads as much as possible not to use a Shared resource.

What if threads have to use global resources?

Using global resources

Is using global resources necessarily not thread safe code?

Again, the answer is… As some of you might have guessed, the answer again is it depends.

There is no problem if the global resource used is initialized only once while the program is running, and then all code uses it read-only, like this:

int global_num = 100; Int func() {return global_num; }Copy the code

We can see that even if a func function uses a global variable, the global variable is initialized only once before it runs and is not modified by the code thereafter, the func function is still thread-safe.

However, if we simply modify func:

int global_num = 100; 

int func() {
  ++global_num;
  return global_num;
}
Copy the code

At this point, func functions are no longer thread-safe, and changes to global variables must be locked.

Thread-local storage

Now let’s make a simple change to the above func function:

__thread int global_num = 100; 

int func() {
  ++global_num;
  return global_num;
}
Copy the code

The func code is once again thread-safe when the global_num variable is prefixed with the keyword __thread.

Why is that?

Thread Local Storage = Thread Local Storage = Thread Local Storage = Thread Local Storage

This variable is a thread-private global variable:

  • Global_num is a global variable

  • Global_num is thread private

Changes made by each thread to global_num do not affect other threads, as the func function is thread-safe because it is a thread-private resource.

Local variables, global variables, function parameters, so the return value of the function.

Function return value

There are also two cases, one where the function returns a value; The other returns a reference to a variable.

1, returns the value

Let’s look at this code:

int func() {
  int a = 100;
  return a;
}
Copy the code

Without a doubt, this code is thread-safe and will return the determined value of 100 no matter how we call this function.

2, return a reference

Let’s change the above code simply:

int* func() {
  static int a = 100;
  return &a;
}
Copy the code

If we call such a function in a multi-threaded environment, you may expect difficult bugs and long nights of overtime.

Obviously, this is not thread-safe code, and the reason for the bug is simple: the value of the variable may have been changed by another thread before you could use it. Since this function uses a static global variable, all threads can change its value if they can get the address of the variable. Since this is a shared resource between threads, don’t write this code unless the boss has a knife to your neck.

Note, however, that there is a special case where this usage can be used to implement a singleton pattern in a design pattern, like this:

class S { public: static S& getInstance() { static S instance; return instance; } private: S() {}Copy the code

Why is that?

Since static local variables are initialized only once no matter how many times we call the func function, this feature makes it easy to implement the singleton pattern.

Finally, let’s look at the case. If we call a non-thread-safe function, is our function thread-safe?

Call non-thread-safe code

If A function A calls another function B, but B is not thread-safe, is function A thread-safe?

Again, the answer is, it depends.

Let’s take a look at this code, which was explained earlier:

int global_num = 0;

int func() {
  ++global_num;
  return global_num;
}
Copy the code

We consider the func function to be non-thread-safe because it uses global variables and modifs them, but if we call the func function like this:

int funcA() {
  mutex l;
   
  l.lock();
  func();
  l.unlock();
}
Copy the code

The funcA function is thread-safe when a lock is added to a func function that is not thread-safe, essentially protecting a global variable indirectly with a lock.

Take another look at this code:

int func(int *num) {
  ++(*num);
  return *num;
}
Copy the code

We generally think of func functions as non-thread-safe, since we don’t know if the passed pointer points to a global variable, but if the code that calls func looks like this:

void funcA() {
  int a = 100;
  func(&a);
}
Copy the code

FuncA is still thread-safe, because the argument passed is a local variable that is private to the thread, and no matter how many threads call funcA, it will not interfere with other threads.

Having looked at thread-safety issues in a variety of situations, let’s conclude with a summary of what can be done to implement thread-safe code.

How to implement thread safety

From the above analysis, implementing thread safety is all about thread private resources and thread shared resources. You need to identify which are thread private and which are shared, which is the core, and then take appropriate measures.

  • Using only thread-private resources without using any global resources is often referred to as stateless code
  • Thread-local storage, if you want to use global resources, can be declared as thread-local storage, because this variable is global, but each thread has a copy of its own, modify it does not affect other threads
  • Read-only. If you must use a global resource, can the global resource be read-only? Multithreading can use a read-only global resource without thread safety issues.
  • Atomic operations are operations that cannot be interrupted by other threads, such as STD ::atomic modified variables in C++. Operations on these types of variables do not require traditional locking because C++ ensures that they cannot be interrupted during the modification process. The kinds of lockless data structures we talk about are usually built on the basis of these atomic operations.
  • Synchronous mutual exclusion, so by this point it’s clear that you have to use global resources in some way, so in that case order in the public place has to be maintained, so how? By means of synchronization or mutual exclusion.

conclusion

If there’s only one sentence you can remember in this article, I hope it’s this one, which is the core of this article:

Implementing thread-safety is all about thread private resources and thread shared resources. You need to identify which are thread private and which are shared, and then take appropriate measures.

I hope this article is helpful for you to write multithreaded programs.

Write in the last

Welcome to pay attention to my public number [calm as code], massive Java related articles, learning materials will be updated in it, sorting out the data will be placed in it.

If you think it’s written well, click a “like” and add a follow! Point attention, do not get lost, continue to update!!