muduo library base source code analysis IV (thread encapsulation)

Posted by nagrgk on Mon, 14 Feb 2022 07:58:46 +0100

Thread identifier: we know that every Linux process has a pid, type pid_t. Obtained by getpid(). POSIX thread under Linux also has an id, type pthread_id, by pthread_self(), the id is maintained by the thread library, and its id space is independent of each process (that is, threads in different processes may have the same id). The thread implemented by POSIX thread library in Linux is actually a process (LWP), but the thread shares some resources with the main process (the process that starts the thread), such as code segment, data segment, etc.

Sometimes we may need to know the real pid of the thread. For example, when process P1 wants to send a signal to a thread in another process P2, it can not use the pid of P2, let alone the pthread id of the thread, but only the real pid of the thread, called tid.

There is a function gettid() that can get tid, but glibc does not implement this function. It can only be obtained by calling syscall in Linux system. return syscall(SYS_gettid)

__thread

 __ Thread, gcc's built-in thread local storage facility

__The thread can only modify POD type

POD type (plain old data) is the original data compatible with C. for example, the type in C language such as structure and integer is POD type, but the class with user-defined constructor or virtual function is not

__The thread string t_obj1(“cppcourse”); / / error, unable to call the constructor of the object

__The thread string* t_obj2 = new string; / / error, initialization can only be a compile time constant

__The thread string* t_obj3 = NULL; / / correct

pthread_atfork

#include <pthread.h>

int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

When fork is called, prepare will be called in the parent process before the internal child process is created. After the internal child process is created successfully, the parent process will call parent and the child process will call child

#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>

void prepare(void)
{
	printf("pid = %d prepare ...\n", static_cast<int>(getpid()));
}

void parent(void)
{
	printf("pid = %d parent ...\n", static_cast<int>(getpid()));
}

void child(void)
{
	printf("pid = %d child ...\n", static_cast<int>(getpid()));
}


int main(void)
{
	printf("pid = %d Entering main ...\n", static_cast<int>(getpid()));

	pthread_atfork(prepare, parent, child);

	fork();

	printf("pid = %d Exiting main ...\n",static_cast<int>(getpid()));

	return 0;
}

Pthread in muduo Library_ Use of atfork():

Fork may be invoked in the main thread or in the sub thread. Fork gets a new process. The new process has only one execution sequence, and only one thread (the thread calling fork is inherited). So use pthread_atfork() after the new child process is created, modify the thread name and cache the tid again.

namespace detail
{

    pid_t gettid()
    {
      return static_cast<pid_t>(::syscall(SYS_gettid));
    }

    void afterFork()
    {
      muduo::CurrentThread::t_cachedTid = 0;
      muduo::CurrentThread::t_threadName = "main";
      CurrentThread::tid();
      // no need to call pthread_atfork(NULL, NULL, &afterFork);
    }

    class ThreadNameInitializer
    {
    public:
      ThreadNameInitializer()
      {
        muduo::CurrentThread::t_threadName = "main";
        CurrentThread::tid();
        pthread_atfork(NULL, NULL, &afterFork);
      }
    };

    ThreadNameInitializer init; // This object is constructed before the main() function
    }
}

In fact, for writing multithreaded programs, we'd better not call fork and write multithreaded and multiprocessing programs, either multithreaded or multiprocessing.

Let's look at an example of Deadlock: a deadlock occurs after multithreading fork

// An example of fork deadlock in multithreaded programs
// An output example:
/*

pid = 19445 Entering main ...
pid = 19445 begin doit ...
pid = 19447 begin doit ...
pid = 19445 end doit ...
pid = 19445 Exiting main ...

The parent process creates a thread and locks the mutex,
The parent process creates a child process and calls doit in the child process. Because the child process copies the memory of the parent process, mutex is in the lock state at this time.
When the parent process copies the child process, it will only copy the execution state of the current thread, and other threads will not copy. Therefore, the child process will be in a deadlock state.
*/
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* doit(void* arg)
{
	printf("pid = %d begin doit ...\n",static_cast<int>(getpid()));
	pthread_mutex_lock(&mutex);
	struct timespec ts = {2, 0};
	nanosleep(&ts, NULL);
	pthread_mutex_unlock(&mutex);
	printf("pid = %d end doit ...\n",static_cast<int>(getpid()));

	return NULL;
}

int main(void)
{
	printf("pid = %d Entering main ...\n", static_cast<int>(getpid()));
	pthread_t tid;
	pthread_create(&tid, NULL, doit, NULL);
	struct timespec ts = {1, 0};
	nanosleep(&ts, NULL);
	if (fork() == 0)
	{
		doit(NULL);
	}
	pthread_join(tid, NULL);
	printf("pid = %d Exiting main ...\n",static_cast<int>(getpid()));

	return 0;
}

Topics: Linux Operation & Maintenance server