Based on Linux socket chat room-multi-threaded server problem handling (02)

      Conduct further analysis based on the remaining issues in the previous article.

      The server uses the following code when creating a child thread:

 pconnsocke = (int *) malloc(sizeof(int));
  *pconnsocke = new_fd;
  
  ret = pthread_create(&tid, NULL, rec_func, (void *) pconnsocke);
  if (ret < 0) 
  {
   perror("pthread_create err");
   return -1;
  } 

    Why do we need to malloc a piece of memory specifically to store this new socket?

To explain the cause of this problem requires some background knowledge:

  1. When Linux creates a new process, the new process will create a main thread;

  2. Each user process has its own address space. The system creates a task_struct for each user process to describe the process. In fact, task_struct is used together with the address space mapping table to represent a process;

  3. Task_struct is also used in Linux to describe a thread, and both threads and processes participate in unified scheduling;

  4. The execution of different threads within the process are different parts of the same program. Each thread executes in parallel and is scheduled asynchronously by the operating system;

  5. Since the address space of the process is private, the system overhead is relatively large when context switching between processes;

  6. Threads created within the same process share the address space of that process.

After understanding these basic knowledge, let's take a look at the parameters passed when the process creates a child thread:

Directly pass the memory address on the stack

Let's first analyze the situation if the address of the local variable new_fd is passed when creating a child thread.

picture

As shown in the picture above:

  1. Create a thread. If we pass parameters as shown in the figure, then new_fd is on the stack. When creating a child thread, we pass the new_fd address to thread1. The address of the thread callback parameter arg is the new_fd address.

  2. Because the main function will always loop without exiting, new_fd will always exist on the stack. In this way, the value 3 of new_fd can indeed be passed to the local variable fd of the child thread, so that the child thread can use this fd to communicate with the client.

  3. But because we are designing a concurrent server model, we have no way to predict when the client will connect to our server. Suppose we encounter an extreme situation where multiple clients connect to the server at the same time, then the main thread must be created at the same time. Multiple sub-threads.

Multiple clients connect to the server simultaneously

picture

As shown in the figure above, the parameter arg of all newly created thread callback functions stores the address of new_fd. If the time interval between client connections is relatively large, there is no problem, but in some extreme cases errors caused by high concurrency may still occur.

Let’s take a look at the extreme calling timing:

first step:

picture

As shown in FIG:

  1. At T1, when client 1 connects to the server, the server's accept function will create a new socket 4;

  2. At T2 time, the sub-thread thread1 is created, and the sub-thread callback function parameter arg points to the memory corresponding to new_fd in the stack.

  3. Assume that at this time, another client wants to connect to the server, and the thread1 page has exhausted the time slice, then the main thread server will be scheduled.

Step two:

picture

As shown in FIG:

  1. At T3, the main thread server accepts the client's connection. The accept function will create a new socket 5 and create a child thread thread2. At this time, the OS schedules thread2;

  2. At T4, thread2 gets the value 5 of new_fd through arg and stores it in fd;

  3. At T5, when the time slice arrives, thread1 is scheduled, and thread1 reads new_fd through arg. At this time, the value of new_fd in the stack has been overwritten by 5;

  4. So there is a situation where two threads use the same fd at the same time.

Although the probability of this situation happening is very low, it does not mean that it will not happen. This bug was encountered by Yiyijun in solving actual projects.

Pass heap memory address

If we use the method of passing the address of the heap, we look at the following figure:

picture

  1. At T1, when client 1 connects to the server, the server's accept function will create a new socket 4, apply for a piece of memory in the heap, use the pointer pconnsocke to point to the memory, and save 4 to the heap;

  2. At T2, the sub-thread thread1 is created, and the sub-thread callback function parameter arg points to the memory pointed to by pconnsocke in the heap.

  3. Assume that at this time, another client wants to connect to the server, and the thread1 page has exhausted the time slice, then the main thread server will be scheduled.

  4. At T3, the main thread server accepts the client's connection. The accept function will create a new socket 5, apply for a piece of memory in the heap, point to the memory with the pointer pconnsocke, save 5 to the heap, and then create the sub-thread thread2. ;

  5. At T4, thread2 points to the memory pointed to by pconnsocke in the heap through arg. The value here is 5 and is stored in fd;

  6. At T5, the time slice arrives, thread1 is scheduled, and thread1 reads fd through arg. At this time, the data bit in the heap is 5;

  7. There will be no situation where two threads use the same fd at the same time.

This knowledge point is a bit hidden, I hope readers will be more careful when using it.

Guess you like

Origin blog.csdn.net/weixin_41114301/article/details/133384304