Article count:1385 Read by:1972833

Featured Content
Account Entry

What process resources are shared between threads?

Latest update time:2021-01-03
    Reads:
Processes and threads are two topics that programmers cannot avoid. These two abstract concepts provided by the operating system are really important.
There is an extremely classic question about processes and threads , that is, what is the difference between processes and threads? I believe that many students may not understand the answer.

Just remember it, but you may not really understand it.

Some students may have memorized this issue very well: "The process is the unit by which the operating system allocates resources, the thread is the basic unit of scheduling, and process resources are shared between threads ."
But do you really understand the last sentence above? What process resources are shared between threads, and what does shared resources mean? How is the mechanism of shared resources implemented? If you don't have an answer to this, it means it's almost impossible to write a multithreaded program that works correctly , and it also means this article is for you.

Reverse Thinking

Charlie Munger often says this: "Think the other way around, always think the other way around." If you are not clear about what process resources are shared between threads, you can also think about it the other way around, that is, what resources are there? Is thread private .

Thread private resources

The essence of thread running is actually the execution of functions. The execution of functions always has a source. This source is the so-called entry function. The CPU starts execution from the entry function to form an execution flow. However, we artificially give the execution flow a name. , this name is called thread.
Since the essence of thread running is the execution of functions, what information does function execution contain?
In the article " What does a function look like in memory when it is running ?" we said that the information when the function is running is stored in the stack frame. The stack frame stores the return value of the function, the parameters for calling other functions, and the usage of the function. The local variables and register information used by the function, as shown in the figure, assume that function A calls function B:

In addition, the information about the instructions executed by the CPU is stored in a register called the program counter. Through this register, we know which instruction to execute next. Since the operating system can suspend the running of the thread at any time, we can know where the thread is suspended and where to continue running by saving and restoring the value in the program counter.
Since the essence of thread running is function running, and function running time information is stored in the stack frame, each thread has its own independent and private stack area.

At the same time, additional registers are needed to save some information when the function is running, such as some local variables. These registers are also private to the thread. It is impossible for one thread to access such register information of another thread .
From the above discussion, we know that so far, the stack area, program counter, stack pointer and registers used by the thread to run functions are private to the thread.
The above information has a unified name, which is thread context.
We have also said that the operating system's scheduling thread needs to interrupt the running of the thread at any time and needs the thread to continue running after being suspended. The reason why the operating system can achieve this relies on thread context information.
Now you should know which ones are thread private.
In addition, the rest are shared resources between threads.
So what's left? And the ones in the picture.

This is actually what the process address space looks like, which means that threads share all the contents in the process address space except thread context information, which means that threads can directly read these contents.
Next we look at these areas separately.

code area

What is stored in the code area in the process address space? Some students may have guessed it from the name. Yes, what is saved here is the code we wrote. To be more precise, it is the compiled executable machine instructions .
So where do these machine instructions come from? The answer is to load it into memory from the executable file. The code area in the executable program is used to initialize the code area in the process address space.

The code area is shared between threads, which means that any function in the program can be executed in the thread. There is no situation where a certain function can only be executed by a specific thread .

data area

The data area in the process address space stores so-called global variables.
What are global variables? The so-called global variables are those variables that you define outside the function. In C language, they look like this:
char c; // 全局变量
void func() { }
The character c is a global variable, which is stored in the data area in the process address space.

During the programmer's running time, that is, run time, there is only one instance of the global variable in the data area, and all threads can access the global variable .
It is worth noting that there is a special type of "global variables" in the C language, which are variables modified with the static keyword, like this:
void func(){ static int a = 10;}
Note that although variable a is defined inside the function, variable a still has the characteristics of a global variable , which means that variable a is placed in the data area of ​​the process address space. The variable still exists even after the function is executed , while ordinary local variables The variables are recycled together with the function stack frame when the function call ends, but the variable a here will not be recycled because it is placed in the data area.
Such a variable is also visible to each thread, which means that each thread can access the variable.

Heap area

The heap area is familiar to programmers. The data we use malloc or new in C/C++ is stored in this area. Obviously, as long as the address of the variable, that is, the pointer, any thread can access the pointer. Data , so the heap area is also a resource shared by threads and belongs to the process.

stack area

Oh, wait! Didn’t you just say that the stack area is a thread-private resource? Why are you talking about the stack area now?
Indeed, from the abstract concept of thread, the stack area is private to the thread. However, from the actual implementation point of view, the rule that the stack area is private to the thread is not strictly followed . What does this sentence mean?
Generally speaking, please note that the wording here is usually . Generally speaking, the stack area is private to the thread. Since there are normal times, there are unusual times.
Not usually because unlike the strict isolation between process address spaces, the thread's stack area is not protected by a strict isolation mechanism, so if one thread can get a pointer from another thread's stack frame, then the thread can change The stack area of ​​another thread means that these threads can arbitrarily modify the variables that belong to the stack area of ​​another thread.

This gives programmers great convenience to a certain extent, but at the same time, it can also lead to bugs that are extremely difficult to troubleshoot.
Imagine that your program is running well, but suddenly something goes wrong at some point. After locating the line of code that caused the problem, you can’t find out the cause. Of course you can’t find out the cause of the problem, because your program doesn’t have anything to begin with. The problem is that someone else's problem caused your function stack frame data to be written incorrectly, resulting in a bug. Such problems are usually difficult to find out. You need to be very familiar with the overall project code. Some commonly used debugging tools may not be very useful at this time. It worked.
Having said so much, students may ask, how does one thread modify data that belongs to other threads?
Next we will explain it with a code example.

Modify thread private data

Don't worry, the following code is simple enough:
void thread(void* var) {    int* p = (int*)var;    *p = 2;}
int main() {    int a = 1; pthread_t tid; pthread_create(&tid, NULL, thread, (void*)&a); return 0;}
What does this code mean?
First, we defined a local variable in the main thread's stack area, which is the line of code int a= 1. Now we already know that the local variable a belongs to the main thread's private data, but then we create another thread.
In the newly created thread, we pass the address of variable a to the newly created thread in the form of a parameter, and then let me take a look at the thread function.
In the newly created thread, we obtain the pointer of variable a, and then modify it to 2, which is this line of code. We modify the private data belonging to the main thread in the newly created thread.

You should understand now that although the stack area is private data of the thread, since no protection mechanism is added to the stack area, the stack area of ​​a thread is visible to other threads, which means that we can modify the stack area belonging to any thread. stack area.
As we said above, this brings great convenience to programmers but also brings endless trouble. Just imagine the above code. If it is really needed by the project, then there is nothing wrong with writing the code like this, but if the above new If a thread is created because a bug modifies private data belonging to other threads, it will be difficult to locate the problem because the bug may be far away from the line of code where the problem is exposed . Such problems are usually difficult to troubleshoot.

dynamic link library

In addition to those discussed above, there are actually other contents in the process address space. What else is there?
This starts with executable programs.
What is an executable program? In Windows, it is the exe file we are familiar with, and in the Linux world, it is the ELF file. These programs that can be run directly by the operating system are what we call executable programs.
So where did the executable program come from?
Some students may say, nonsense, isn’t it just generated by the compiler?
Actually this answer is only half correct.
Assume that our project is relatively simple and only has a few source code files. How does the compiler convert these source code files into a final executable program?
It turns out that after the compiler translates the executable program into machine instructions, there is another important step, which is linking. After the linking is completed, the executable program is generated.
It is the linker that completes the linking process.

The linker can have two linking methods, which are static linking and dynamic linking .
Static linking means that all machine instructions are packaged into the executable program. Dynamic linking means that we do not package the dynamically linked parts into the executable program, but store them in the memory after the executable program is run. Find the part of code that is dynamically linked. This is the so-called static link and dynamic link.
An obvious benefit of dynamic linking is that the size of the executable program will be very small. Just like when we look at an exe file under Windows, it may be very small, so the exe is likely to be generated by dynamic linking .
The library generated by the dynamic link part is the dynamic link library we are familiar with. It is a file ending with DLL under Windows and a file ending with so under Linux.
Having said all that, what does this have to do with thread shared resources?
It turns out that if a program is generated by dynamic linking, then part of its address space contains the dynamic link library , otherwise the program will not run. This part of the address space is also shared by all threads.

That is to say, all threads in the process can use the code in the dynamic link library.
The above is actually a very brief introduction to the topic of links. For a detailed discussion of the topic of links, please refer to the series of articles " Comprehensive Understanding of Linkers ".

document

Finally, if the program opens some files during running, the open file information is also stored in the process address space, and the files opened by the process can also be used by all threads. This is also a shared resource between threads.

One More Thing: TLS

Is that all this article?
In fact, there is one item about thread private data that has not been explained in detail, because if you continue to explain it, this article will be full, and the part already explained in this article is enough. The remaining point is just a supplement, that is, the elective part. If you're not interested in it, you can skip it, no problem.
There is another technology for thread private data, which is thread local storage, Thread Local Storage, TLS.
What does it mean?
In fact, it can be seen from the name that the so-called thread local storage refers to the variables stored in this area, which have two meanings:
  • Variables stored in this area are global variables and can be accessed by all threads

  • Although it seems that all threads access the same variable, the global variable belongs to one thread alone, and modifications to this variable by one thread are not visible to other threads.

After saying so much, you still don’t understand? It doesn't matter. If you still don't understand after reading these two pieces of code, hit me.
Let’s look at the first piece of code first. Don’t worry, this code is very, very simple:
int a = 1; // 全局变量
void print_a() { cout<<a<<endl;}
void run() { ++a; print_a();}
void main() { thread t1(run); t1.join();
thread t2(run); t2.join();}
Well, this code is simple enough. The above code is written in C++11. Let me explain what this code means.
  • First we create a global variable a with an initial value of 1
  • Secondly, we created two threads, each thread added 1 to the variable a
  • The thread's join function indicates that the thread will continue to run the next code after it has finished running.
So what will be printed when this code is run?
The initial value of the global variable a is 1. After the first thread adds 1, a becomes 2, so 2 will be printed; after the second thread adds 1 again, a becomes 3, so 3 will be printed. Let's take a look at the operation. result:
23
It seems that our analysis is correct. The global variable finally becomes 3 after the two threads add 1 respectively.
Next, we slightly modify the definition of variable a and leave the other codes unchanged:
__thread int a = 1; // 线程局部存储
We see that a __thread keyword is added in front of the global variable a for modification. In other words, we tell the compiler to place the variable a in thread local storage. What changes will this bring to the program?
You will know by simply running:
22
Is it what you thought? Some students may be surprised, why do we obviously add variable a twice, but why does it still print 2 instead of 3 during the second run?
Think about why.
It turns out that this is the role of thread local storage. The modification of variable a by thread t1 will not affect thread t2. After thread t1 adds variable a to 1, it becomes 2, but for thread t2, variable a is still is 1, so adding 1 is still 2.
Therefore, thread-local storage allows you to use a global variable that is unique to the thread . In other words, although the variable can be accessed by all threads, the variable has a copy in each thread, and modifications to the variable by one thread will not affect other threads.

Summarize

Well, I didn't expect that there would be so many knowledge points behind the simple sentence "threads share process resources" in the textbook. The knowledge in the textbook seems easy, but it is not simple .
I hope this article can help everyone understand how processes and threads can be.

 
EEWorld WeChat Subscription

 
EEWorld WeChat Service Number

 
AutoDevelopers

About Us Customer Service Contact Information Datasheet Sitemap LatestNews

Room 1530, Zhongguancun MOOC Times Building,Block B, 18 Zhongguancun Street, Haidian District,Beijing, China Tel:(010)82350740 Postcode:100190

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号