Use of C language thread library

1. Thread overview Thread is a lightweight pr...
1. Thread overview
2. Create thread
3. Thread exit
4. Thread recycling
5. Thread separation
6. Other thread functions

1. Thread overview

Thread is a lightweight process (LWP). In Linux environment, the essence of thread is still a process. The program running on the computer is a combination of a group of instructions and instruction parameters. The instructions control the computer according to the established logic. The operating system will allocate system resources in the unit of process. It can be understood that process is the smallest unit of resource allocation, and thread is the smallest unit of scheduling and execution of the operating system.

Let's first understand the conceptual differences between threads and processes:

1. The process has its own independent address space, and multiple threads share the same address space

  • Threads save more system resources, and the efficiency can not only be maintained, but also be higher
  • In one address space, multiple threads are exclusive: each thread has its own stack area and register (managed in the kernel)
  • In an address space, multiple threads share code segments, heap areas, global data areas, and open files (file descriptor tables)

2. Thread is the smallest execution unit of a program, and process is the smallest resource allocation unit in the operating system

  • Each process corresponds to a virtual address space. A process can only grab one CPU time slice
  • One address space can be divided into multiple threads, which can grab more CPU time slices on the basis of effective resources

    3.CPU scheduling and switching: the context switching of threads is much faster than that of processes
    Context switching: the process / thread time-sharing reuses the CPU time slice. Before switching, the state of the previous task will be saved. The next time it switches back to this task, the state will continue to run. The process from saving to reloading the task is a context switching.

4. Threads are cheaper, start faster, exit faster, and have less impact on system resources
When dealing with multitasking programs, using multithreading has more advantages than using multiple processes, but the more threads are not the better. How to control the number of threads?

  1. File IO operation: file IO has low CPU utilization, so CPU time slices can be reused time-sharing. The number of threads = 2 * the number of CPU cores (the highest efficiency)
  2. Processing complex algorithms (mainly CPU operations, high pressure), the number of threads = the number of CPU cores (the highest efficiency)

2. Create thread

2.1 thread function

Each thread has a unique thread ID of pthread type_ t. This ID is an unsigned long integer. If you want to get the thread ID of the current thread, you can call the following function:

pthread_t pthread_self(void); // Returns the thread ID of the current thread

In a process, calling a thread to create a function can get a child thread, which is different from the process. It needs to specify a processing function for every thread created, otherwise the thread will not work.

#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); // Compile and link with -pthread. The name of the thread library is pthread. The full name is libpthread.so libptread.a

Parameters:

  • Thread: outgoing parameter, which is an unsigned long integer. If the thread is created successfully, the thread ID will be written to the memory pointed to by this pointer
  • attr: the attribute of the thread. Generally, the default attribute can be used. Write NULL
  • start_routine: function pointer, the processing action of the created sub thread, that is, the function is executed in the sub thread.
  • arg: passed as an argument to start_ The routine pointer points to the inside of the function

Return value: 0 is returned for successful thread creation, and the corresponding error number is returned for failed thread creation

2.2 creating threads

The following is the sample code for creating a thread. During the creation process, make sure that the thread function written is consistent with the specified function pointer type: void *(*start_routine) (void *):

// pthread_create.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // Processing code of child thread void* working(void* arg) { printf("I'm a child thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); } return NULL; } int main() { // 1. Create a child thread pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("The child thread was created successfully, thread ID: %ld\n", tid); // 2. The sub thread will not execute the following code, but the main thread will execute it printf("I'm the main thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // Take a break, take a break // sleep(1); return 0; }

When compiling the test program, you will see the following error message:

$ gcc pthread_create.c /tmp/cctkubA6.o: In function `main': pthread_create.c:(.text+0x7f): undefined reference to `pthread_create' collect2: error: ld returned 1 exit status

The reason for the error is that the compiler cannot link to the thread library file (dynamic library), which needs to be specified through parameters during compilation. The name of the dynamic library is libpthread.so, and the required parameter is - l. according to the rules, the final form should be written as: - lpthread (there can be spaces between parameters and parameter values). The correct compile command is:

# pthread_ The create function is defined in a library, and the library name pthread needs to be added during compilation $ gcc pthread_create.c -lpthread $ ./a.out The child thread was created successfully, thread ID: 139712560109312 I'm the main thread, thread ID: 139712568477440 i = 0 i = 1 i = 2

In the printed log output, why did the sub thread processing function not finish executing (only part of the log output of the sub thread was seen)?

The main thread is running all the time. A sub thread is created during execution, indicating that the main thread has a CPU time slice. After the code is executed within this time slice, the main thread exits. After the child thread is created, it needs to grab the CPU time slice. If it cannot be grabbed, it cannot run. If the main thread exits, the virtual address space will be released and the child threads will be destroyed together. However, if a child thread exits, the main thread is still running and the virtual address space still exists.

The conclusion is that without human intervention, the life cycle of virtual address space is the same as that of the main thread, and has nothing to do with the sub thread.

The current solution: let the child thread finish executing and the main thread exit. You can add the suspend function sleep() to the main thread;

3. Thread exit

When writing a multithreaded program, if you want the thread to exit without releasing the virtual address space (for the main thread), you can call the thread exit function in the thread library. As long as you call this function, the current thread will exit immediately and will not affect the normal operation of other threads, It can be used either in the child thread or in the main thread.

#include <pthread.h> void pthread_exit(void *retval);
  • Parameter: the data carried when the thread exits. The main thread of the current sub thread will get the data. Specify NULL if not required

The following is the sample code of thread exit, which can be called at the required position of any thread:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // Processing code of child thread void* working(void* arg) { sleep(1); printf("I'm a child thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { if(i==6) { pthread_exit(NULL); // Exit child thread directly } printf("child == i: = %d\n", i); } return NULL; } int main() { // 1. Create a child thread pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("The child thread was created successfully, thread ID: %ld\n", tid); // 2. The sub thread will not execute the following code, but the main thread will execute it printf("I'm the main thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // The main thread calls the exit function to exit, and the address space will not be released pthread_exit(NULL); return 0; }

4. Thread recycling

4.1 thread function

Like a process, when a child thread exits, its kernel resources are mainly recycled by the main thread. The thread recycling function provided in the thread library is called pthread_join() is a blocking function. If there are still sub threads running, calling this function will block. The sub thread exits the function to unblock and recycle resources. When the function is called once, only one sub thread can be recycled. If there are multiple sub threads, recycling is required.

In addition, the thread recycling function can also obtain the data passed when the child thread exits. The function prototype is as follows:

#include <pthread.h> // This is a blocking function. When a child thread runs this function, it will block // When the sub thread exits, the function unblocks and reclaims the corresponding sub thread resources, which is similar to the function wait() used by the recycling process int pthread_join(pthread_t thread, void **retval);

Parameters:

  • Thread: the thread ID of the child thread to be recycled
  • retval: the secondary pointer refers to the address of the primary pointer. It is an outgoing parameter. Pthread is stored in this address_ The data passed by exit() can be specified as NULL if this parameter is not required

Return value: 0 is returned for successful thread recycling, and the error number is returned for failed recycling.

4.2 reclaiming sub thread data

Pthread can be used when the child thread exits_ The parameter of exit () transfers the data out. When recycling this sub thread, you can use phread_ The second parameter of join () to receive the data passed by the child thread. There are many processing methods for received data, which are listed below:

4.2.1 using sub thread stack

Through function pthread_exit(void retval); It can be seen that when the sub thread exits, it needs to record the data into a piece of memory. The address of the memory where the data is stored is transmitted through the parameter, not the specific data. Because the parameter is of void type, all this universal pointer can point to any type of memory address. Let's look at the first method. Save the child thread exit data in the child thread's own stack area:

// pthread_join.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // Define structure struct Persion { int id; char name[36]; int age; }; // Processing code of child thread void* working(void* arg) { printf("I'm a child thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); if(i == 6) { struct Persion p; p.age =12; strcpy(p.name, "tom"); p.id = 100; // The parameters of this function pass this address to the pthread of the main thread_ join() pthread_exit(&p); } } return NULL; // If the code can't execute to this position, it exits } int main() { // 1. Create a child thread pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("The child thread was created successfully, thread ID: %ld\n", tid); // 2. The sub thread will not execute the following code, but the main thread will execute it printf("I'm the main thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // Block waiting for child thread to exit void* ptr = NULL; // ptr is an outgoing parameter, which points to a valid memory inside the function // The memory address is pthread_ The memory pointed to by the exit() parameter pthread_join(tid, &ptr); // Print information struct Persion* pp = (struct Persion*)ptr; printf("The child thread returns data: name: %s, age: %d, id: %d\n", pp->name, pp->age, pp->id); printf("The child thread resource was successfully reclaimed...\n"); return 0; }

Compile and execute the test program:

# Compile code $ gcc pthread_join.c -lpthread # Execution procedure $ ./a.out The child thread was created successfully, thread ID: 140652794640128 I'm the main thread, thread ID: 140652803008256 i = 0 i = 1 i = 2 I'm a child thread, thread ID: 140652794640128 child == i: = 0 child == i: = 1 child == i: = 2 child == i: = 3 child == i: = 4 child == i: = 5 child == i: = 6 The child thread returns data: name: , age: 0, id: 0 The child thread resource was successfully reclaimed...

Through the printed log, it can be found that the data information returned by the child thread is not obtained in the main thread. The specific reasons are as follows:

If multiple threads share the same virtual address space, each thread has its own memory in the stack area, which is equivalent to that the stack area is divided equally by these threads. When the thread exits, the memory of the thread in the stack area will be recycled. Therefore, with the exit of the sub thread, the data written into the stack area will be released.

4.2.2 using global variables

Threads in the same virtual address space cannot share stack data, but can share global data area and heap data. Therefore, when the sub thread exits, the outgoing data can be stored in global variables, static variables or heap memory. In the following example, the data is stored in the global variable:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // Define structure struct Persion { int id; char name[36]; int age; }; struct Persion p; // Define global variables // Processing code of child thread void* working(void* arg) { printf("I'm a child thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); if(i == 6) { // Use global variables p.age =12; strcpy(p.name, "tom"); p.id = 100; // The parameters of this function pass this address to the pthread of the main thread_ join() pthread_exit(&p); } } return NULL; } int main() { // 1. Create a child thread pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("The child thread was created successfully, thread ID: %ld\n", tid); // 2. The sub thread will not execute the following code, but the main thread will execute it printf("I'm the main thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // Block waiting for child thread to exit void* ptr = NULL; // ptr is an outgoing parameter, which points to a valid memory inside the function // The memory address is pthread_ The memory pointed to by the exit() parameter pthread_join(tid, &ptr); // Print information struct Persion* pp = (struct Persion*)ptr; printf("name: %s, age: %d, id: %d\n", pp->name, pp->age, pp->id); printf("The child thread resource was successfully reclaimed...\n"); return 0; }
4.2.3 using mainline stack

Although each thread has its own stack space, multiple threads in the same address space can access each other's stack space data. In many cases, it is necessary to recover the sub thread resources in the main thread, so the main thread generally exits last. For this reason, the data returned by the sub thread is saved to the stack memory of the main thread in the following program:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // Define structure struct Persion { int id; char name[36]; int age; }; // Processing code of child thread void* working(void* arg) { struct Persion* p = (struct Persion*)arg; printf("I'm a child thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); if(i == 6) { // Use the stack memory of the main thread p->age =12; strcpy(p->name, "tom"); p->id = 100; // The parameters of this function pass this address to the pthread of the main thread_ join() pthread_exit(p); } } return NULL; } int main() { // 1. Create a child thread pthread_t tid; struct Persion p; // The stack memory of the main thread is passed to the child thread pthread_create(&tid, NULL, working, &p); printf("The child thread was created successfully, thread ID: %ld\n", tid); // 2. The sub thread will not execute the following code, but the main thread will execute it printf("I'm the main thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // Block waiting for child thread to exit void* ptr = NULL; // ptr is an outgoing parameter, which points to a valid memory inside the function // The memory address is pthread_ The memory pointed to by the exit() parameter pthread_join(tid, &ptr); // Print information printf("name: %s, age: %d, id: %d\n", p.name, p.age, p.id); printf("The child thread resource was successfully reclaimed...\n"); return 0; }

In the above procedure, call pthread_. Create() creates a child thread, passes the address of the stack space variable p in the main thread to the child thread, and writes the data to be passed to this memory in the child thread. In other words, in the main() function of the program, the data from the sub thread can be read out through the pointer variable ptr or the structure variable P.

5. Thread separation

In some cases, the main thread in the program has its own business processing process. If the main thread is responsible for resource recovery of sub threads, pthread is called_ Join () as long as the child thread does not exit, the main thread will always be blocked, and the tasks of the main thread cannot be executed.

The thread separation function pthread is provided in the online library function_ Detach(), after calling this function, the specified sub thread can be separated from the main thread. When the sub thread exits, the kernel resources occupied by it will be taken over and recycled by other processes of the system. After thread separation, pthread is used in the main thread_ Join () can't reclaim child thread resources.

#include <pthread.h> // The parameter is the thread ID of the child thread, and the main thread can be separated from the child thread int pthread_detach(pthread_t thread);

In the following code, a sub thread is created in the main thread and the thread separation function is called to separate the main thread from the sub thread:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // Processing code of child thread void* working(void* arg) { printf("I'm a child thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); } return NULL; } int main() { // 1. Create a child thread pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("The child thread was created successfully, thread ID: %ld\n", tid); // 2. The sub thread will not execute the following code, but the main thread will execute it printf("I'm the main thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // Set the separation of child thread and main thread pthread_detach(tid); // Let the main thread exit by itself pthread_exit(NULL); return 0; }

6. Other thread functions

6.1 thread cancellation

Thread cancellation means killing another thread in one thread under certain circumstances. Using this function to kill a thread requires two steps:

  • Calling thread in thread A to cancel function pthread_cancel specifies to kill thread B. at this time, thread B cannot die
  • In thread B, the process makes a system call (switching from the user area to the kernel area), otherwise thread B can run all the time.

In fact, this is the same as the effect of Qibu duanchang powder and smiling half step epilepsy. It's OK to stay still or not laugh after taking poison

#include <pthread.h> // Parameter is the thread ID of the child thread int pthread_cancel(pthread_t thread);
  • Parameter: thread ID of the thread to kill
  • Return value: 0 is returned when the function call is successful, and non-0 error number is returned when the call fails.

In the following example code, the main thread calls the thread cancellation function. As long as the system call is made in the sub thread, the sub thread will hang up when it reaches this position.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // Processing code of child thread void* working(void* arg) { int j=0; for(int i=0; i<9; ++i) { j++; } // This function calls the system function, so this is an indirect system call printf("I'm a child thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf(" child i: %d\n", i); } return NULL; } int main() { // 1. Create a child thread pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("The child thread was created successfully, thread ID: %ld\n", tid); // 2. The sub thread will not execute the following code, but the main thread will execute it printf("I'm the main thread, thread ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // Kill the child thread. If a system call is made in the child thread, the child thread ends pthread_cancel(tid); // Let the main thread exit by itself pthread_exit(NULL); return 0; }

There are two ways to make system calls:

  1. Call Linux system functions directly
  2. Call standard C library functions. In order to realize some functions, standard C library functions will call relevant system functions on Linux platform

6.2 thread ID comparison

In Linux, the thread ID is essentially an unsigned long integer, so you can directly use the comparison operator to compare the IDs of two threads, but the thread library can be used across platforms, pthread on some platforms_ T may not be a simple shaping. In this case, the comparison function must be used to compare the IDs of two threads. The function prototype is as follows:

#include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2);
  • Parameters: t1 and t2 are the thread ID s of the threads to be compared
  • Return value: if two thread ID s are equal, return a non-zero value; if not, return 0

11 November 2021, 06:10 | Views: 7743

Add new comment

For adding a comment, please log in
or create account

0 comments