Linux system programming multi-threading (C++)

Table of contents

【1】Introducing how to view address spaces and lists

【2】What is a thread?

【3】Advantages of threads

【4】Disadvantages of threads

【5】Thread exception

【6】Thread usage

【7】Thread VS process

【8】Linux thread control

【8.1】View lightweight thread instructions

【8.2】Thread creation

【8.2.1】POSIX thread library

【8.2.2】Create thread

【8.2.3】Create a thread at one time

【8.2.4】Create multiple threads at one time

【8.2】Thread termination

【8.3】Thread cancellation

【8.4】Thread waiting

【8.5】Separate threads

【9】Thread ID and process address space layout


【1】Introducing how to view address spaces and lists

  • The address space is the window of resources that a process can see.

  • The page table determines the resources that the process actually owns.

  • By properly dividing the resources of the address space + page table, we can classify all the resources of a process .

【2】What is a thread?  

  • An execution route in a program is called a thread. A more accurate definition is: a thread is "a control sequence within a process."

  • A thread is a flow of execution within a process.

  • All processes have at least one thread of execution.

  • Threads run inside the process, essentially running in the process address space.

  • In the Linux system, in the eyes of the CPU, the PCB seen is more lightweight than the traditional process.

  • Through the process virtual address space, you can see most of the resources of the process. By rationally allocating process resources to each execution flow, a thread execution flow is formed.

        Because we can divide the resources of the process through virtual address space + page table, the execution intensity of a single "process" must be more detailed than the previous process!

[Thinking] If our OS really wants to specifically design the concept of "threads", will the OS manage this thread in the future?

        A special data structure must be designed for threads to represent thread objects --- called TCB (this is the case in Windows).

        From a purely thread scheduling perspective, threads and processes overlap in many places. All Linux engineers do not want to specifically design corresponding data structures for Linux "threads", but directly reuse PCBs and use PCBs to represent "threads" within Linux. ".

        Threads run inside the process. Threads run in the address space of the process and own part of the resources of the process!

Thinking: When threads come, so do problems!

  • What is a process?

Before: process = kernel data structure + code and data corresponding to the process.

Now: From a kernel perspective, the entity by which a process allocates system resources.

  • What is a thread?

Now: The basic unit of CPU scheduling.

  • How do we view the corresponding process concept when we studied process before? Does it conflict with today's thread?

Before: The basic entity responsible for system resources, but there is only one execution flow inside!

Now: there can be multiple execution streams within a process!

  • From the perspective of the CPU

Before: calling process

Now: Calling a branch in the process

But: The CPU does not care, the task_struct fed to the CPU today <= the meaning of the historical tesk_struct

Thinking: Based on the above summary, are there any real threads in the Linux kernel?

        No! , Linux uses process PCB to simulate threads. It is a completely own set of thread solutions. From the perspective of the CPU, each PCB can be called a lightweight process . Linux* threads are scheduled by the CPU. The basic unit , and the process is the basic unit responsible for allocating system resources . The process is used to apply for resources as a whole, and the thread is used to reach out to the process to request resources. There is no real meaning of thread in Linux.

【3】Advantages of threads

  • Creating a new thread is much less expensive than creating a new process.

  • Compared with switching between processes, switching between threads requires the operating system to do much less work.

  • Process: Switch page table && virtual address space && switch PCB && context switch.

  • Thread: switch PCB&& context switch.

  • Thread switching cache is not used

  • Threads occupy much fewer resources than processes.

  • Can fully utilize the parallel number of multi-processors.

  • While waiting for the slow I/O operation to complete, the program can perform other computing tasks.

  • For computationally intensive applications, in order to run on a multi-processor system, the calculations are decomposed into multiple threads.

  • In I/O-intensive applications, in order to improve performance, I/O operations are overlapped. Threads can wait for different I/O operations at the same time.

【4】Disadvantages of threads

  • Performance loss: A computationally intensive thread that is rarely blocked by external events often cannot share the same processor with other threads. If the number of compute-intensive threads exceeds the available processors, there may be a large performance loss, where the performance loss refers to the addition of additional synchronization and scheduling overhead, while the available resources remain unchanged.

  • Reduced robustness: Writing multi-threads requires more comprehensive and in-depth consideration. In a multi-threaded program, the possibility of adverse effects due to subtle deviations in time allocation or sharing of variables that should not be shared is very high. In other words, there is a lack of protection between threads.

  • Lack of access control: The process is the basic granularity of access control. Calling certain OS functions in one thread will affect the entire process.

  • Increased programming difficulty: Writing and debugging a multi-threaded program is much more difficult than a single-threaded program.

Since there are no real threads in Linux - and the OS only recognizes threads, users (programmers) also only recognize threads. Linux cannot directly provide a system call interface for creating threads, but can only provide us with a system call interface for creating lightweight processes. interface!

【5】Thread exception

  • If division by zero occurs in a single thread, the wild pointer problem will cause the thread to crash, and the process will also crash.

  • The thread is the execution branch of the process. An exception in the thread is similar to an exception in the process, which triggers the signal mechanism and terminates the process. When the process terminates, all threads in the process will exit immediately.

【6】Thread usage

  • Reasonable use of multi-threading can improve the execution efficiency of CPU-intensive programs.

  • Reasonable use of multi-threading can improve the user experience of IO-intensive programs (for example, in life, we download development tools while writing code, which is a manifestation of multi-threading).

【7】Thread VS process

  • Process is the basic unit of resource allocation

  • Thread is the basic unit of scheduling

  • Threads share process data, but also have their own portion of data:

  • Thread ID

  • a set of registers

  • stack

  • errno

  • signal mask word

  • Scheduling priority

Multiple threads of the process share the same address space, so Text Segment and Data Segment are shared. If a function is defined, it can be called in each thread. If a global variable is defined, it can be accessed in each thread, except In addition, each thread also shares the following process resources and environment:

  • file descriptor table

  • Each signal processing method (SIG_ IGN, SIG_ DFL or custom signal processing function)

  • current working directory

  • user id and group id

[The most ideal creation rationality] The number of CPU cores determines the number of threads, and the number of CPUs determines the number of processes.

【8】Linux thread control

【8.1】View lightweight thread instructions

// 查看轻量级进程指令
ps -aL

【8.2】Thread creation

【8.2.1】POSIX thread library

  • Thread-related functions form a complete series, and the names of most functions begin with "pthread_" .

  • To use these function libraries, introduce the header <phtread.h> .

  • Use the "-lpthread" option of the compiler command when linking these thread libraries.

【8.2.2】Create thread

// 功能:创建一个新的线程
// 原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);

// 参数
// thread:返回线程ID
// attr:设置线程的属性,attr为NULL表示使用默认属性
// start_routine:是个函数地址,线程启动后要执行的函数
// arg:传给线程启动函数的参数
// 返回值:成功返回0;失败返回错误码

【Error check】

  • Some traditional functions return 0 on success, -1 on failure, and assign a value to the global variable errno to indicate the error.

  • The pthreads function does not set the global variable errno when an error occurs (as most other POSIX functions do). Instead, the error code is returned through the return value.

  • pthreads also provides the errno variable within the thread to support other code that uses errno. For errors in the pthreads function, it is recommended to determine by the return value, because reading the return value is less expensive than reading the errno variable in the thread.

【Example code】

// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11

myThread:Thread.cc
	$(cc) -o $@ $^ $(standard) -lpthread

.PHONY:clean
clean:
	rm -rf myThread 

// Thread.cc文件》》》》》》》》》》》》》》》》
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;

/* 线程函数 */
void* thread_rotine(void* args) {
    const char* buffer = (const char*)args;

    while(true) {
        cout << "我是新线程,我正在运行,我的名字叫做:" << buffer << endl;
        sleep(1);
    }
}

/* 入口函数 */
int main() {
    // 线程的描述符.
    pthread_t tid;
    // 参数传递依次是:描述符 nullptr 线程函数 传送的参数
    pthread_create(&tid, nullptr, thread_rotine, (void*)"thread_one");

    // 主线程:
    while(true) {
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "0x%x", tid);

        cout << "我是主线程,我正在运行,我的tid是:" << buffer << endl; 
        sleep(1);
    }

    return 0;
}

// 打印结果:
我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one

我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one

我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one

我是主线程,我正在运行,我的tid是:0x33d67700
我是新线程,我正在运行,我的名字叫做:thread_one

 

[Concept] Once a thread is created, almost all resources are shared by all threads!

// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11

myThread:Thread.cc
	$(cc) -o $@ $^ $(standard) -lpthread

.PHONY:clean
clean:
	rm -rf myThread

// Thread.cc文件》》》》》》》》》》》》》》》》
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;

/* 测试共享的全局变量和函数 */ 
int g_num = 0;

const string Func() {
    return "我是一个独立的函数!";
}

/* 线程函数 */
void* thread_rotine(void* args) {
    const char* buffer = (const char*)args;

    while(true) {
        cout << "我是新线程,我正在运行,我的名字叫做:" << buffer << " " << Func() << " " << g_num++ << endl;
        sleep(1);
    }
}

/* 入口函数 */
int main() {
    // 线程的描述符.
    pthread_t tid;
    // 参数传递依次是:描述符 nullptr 线程函数 传送的参数
    pthread_create(&tid, nullptr, thread_rotine, (void*)"thread_one");

    // 主线程:
    while(true) {
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "0x%x", tid);

        cout << "我是主线程,我正在运行,我的tid是:" << buffer << " " << Func() << " " << g_num << endl;
        sleep(1);
    }

    return 0;
}

// 打印结果:现象就是线程在进程中的资源共享
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 2
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 3
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 3
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 4
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 4
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 4
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 5
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 5
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 6
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 6
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 7
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 7
我是主线程,我正在运行,我的tid是:0x78002700 我是一个独立的函数! 8
我是新线程,我正在运行,我的名字叫做:thread_one 我是一个独立的函数! 8

[Concept] Threads must also have their own private resources. What resources should be private to threads?

  • PCB properties are private.

  • There must be a certain private context structure.

  • Each thread must have its own independent stack structure.

[Concept] Compared with threads and processes, switching between threads requires the operating system to do much less work.

  • Process: Switch page table && virtual address space && switch PCB && context switch.

  • Thread: switch PCB && context switch.

【8.2.3】Create a thread at one time

// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11

myThread:Thread.cc
	$(cc) -o $@ $^ $(standard) -lpthread

.PHONY:clean
clean:
	rm -rf myThread

// Thread.cc文件》》》》》》》》》》》》》》》》
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;

/* 创建的新线程 */
void* Start_Routine(void* args) {
    // 将参数转换为有效参数
    const string paramName = static_cast<const char*>(args);

    // 创建的线程执行
    while(true) {
        cout << "new thread create success, name:Start_Routine " << endl;
        sleep(1);
    }
}

/* 程序入口函数 */
int main() {
    // 创建线程
    pthread_t tid;
    pthread_create(&tid, nullptr, Start_Routine, (void*)"Thread One");

    // 主线程执行
    while(true) {
        cout << "new thread create success, name:main thread" << endl;
        sleep(1);
    }
    return 0;
}

// 打印结果:
new thread create success, name:Start_Routine 
new thread create success, name:main thread
new thread create success, name:Start_Routine 
new thread create success, name:main thread
new thread create success, name:Start_Routine 
new thread create success, name:main thread

【8.2.4】Create multiple threads at one time

// Makefile 文件》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11

myThread:Thread.cc
	$(cc) -o $@ $^ $(standard) -lpthread

.PHONY:clean
clean:
	rm -rf myThread

// Thread.cc文件》》》》》》》》》》》》》》》》
// 监控脚本 while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData {
public:
    pthread_t tid;          // 线程的描述符
    char nameBuffer[64];    // 线程的名称
};

/* 抽象的线程函数 */
// start_routine函数现在是被几个线程执行呢? 10 
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void*Start_Routine(void* args) {
    sleep(1);

    // 获取到传递的void*参数
    // 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
    ThreadData* td = static_cast<ThreadData*>(args);

    // int cnt = 10; // 这里的操作是为了演示线程具有独立的栈结构!
    while(true) {
        // cout << "cnt:" << &cnt << endl;
        cout << "new thread create success,name:" << td->nameBuffer << " " << td->tid << endl;
        sleep(5);
    }

    delete td;
    return nullptr;
}

/* 入口函数 */
int main() {
    vector<ThreadData*> threads;

#define NUM 10 // 宏定义,默认一次性创建10个进程
    // 一次性创建多个进程
    for(int i = 0; i < NUM; i++) {
        // 在堆中创建线程的单集合
        ThreadData* td = new ThreadData();
        // 封装ThreadData类
        snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i + 1);
        pthread_create(&td->tid, nullptr , Start_Routine, td);
        // 保留每个线程的信息
        threads.push_back(td);
    }

    // 打印已创建出来的线程清单
    for(auto v : threads) {
        cout << "create thread:" << v->nameBuffer << " " << v->tid << " success!" << endl;
    }

    // 主线程执行
    while(true) {
        cout << "new thread create success,name:main tread!" << endl;
        sleep(5);
    }

    return 0;
}
// 打印结果:
create thread:thread,1 139744236070656 success!
create thread:thread,2 139744227677952 success!
create thread:thread,3 139744219285248 success!
create thread:thread,4 139744210892544 success!
create thread:thread,5 139744202499840 success!
create thread:thread,6 139744194107136 success!
create thread:thread,7 139744185714432 success!
create thread:thread,8 139744177321728 success!
create thread:thread,9 139744168929024 success!
create thread:thread,10 139744160536320 success!
new thread create success,name:main tread!
new thread create success,name:thread,7 140342020540160
new thread create success,name:thread,5 140342037325568
new thread create success,name:thread,2 140342062503680
new thread create success,name:thread,1 140342070896384
new thread create success,name:thread,10 140341995362048
new thread create success,name:thread,6 140342028932864
new thread create success,name:thread,8 140342012147456
new thread create success,name:thread,9 140342003754752
new thread create success,name:thread,3 140342054110976
new thread create success,name:thread,4 140342045718272

【8.2】Thread termination

If you need to terminate only a certain thread without terminating the entire process, there are three methods:

  1. Return from thread function. This method is not applicable to the main thread. Return from the main function is equivalent to calling exit.

  2. A thread can terminate itself by calling pthread_exit.

  3. A thread can call pthread_cancel to terminate another thread in the same process.

【pthread_exit function】 |【return nullptr】

// 功能:线程终止
// 原型
void pthread_exit(void *value_ptr);
// 参数
// value_ptr:value_ptr不要指向一个局部变量。
// 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

// 【注意】需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc
// 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

【Test code】

// 监控脚本 
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done

#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;

/* 线程数据类 */
class ThreadData{
public:
    pthread_t tid;
    char nameBuffer[64];
};

// start_routine函数现在是被几个线程执行呢? 10 
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
    // 演示打印!
    sleep(1);

    // 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
    ThreadData *td = static_cast<ThreadData*>(args);  // 安全的进行强制类型转换。
    // exit(-1);    // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
    // return nullptr;      // ok
    pthread_exit(nullptr);  // oK

    int cnt = 10;
    char tempStr[64] = {'\0'};
    while(cnt){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
        cout << tempStr;
        sleep(1);
    }

    // 释放空间
    delete td;
    return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
} 

int main(){
    vector<ThreadData *> tids;
#define NUM 10
    // 一次性创建多个进程.
    for(int i = 1; i <= NUM; i++){
        // 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
        ThreadData* td = new ThreadData();
        snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
        pthread_create(&td->tid, nullptr, start_routine, td);
        // 每个进程进行保存!
        tids.push_back(td);
    }

    // 打印线程清单
    for(auto& e : tids){
        cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
    }

    char tempStr[64] = {'\0'};
    while(true){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
        cout << tempStr;
        sleep(1);
    }
    return 0;
}

【8.3】Thread cancellation

// 注意:线程可以被取消,线程要取消,前提是这个线程已经跑起来了!
// 功能:取消一个执行中的线程
// 原型
int pthread_cancel(pthread_t thread);
// 参数
// thread:线程ID
// 返回值:成功返回0;失败返回错误码

【Test code】

// 监控脚本 
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done

#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;

/* 线程数据类 */
class ThreadData{
public:
    long long number;
    pthread_t tid;
    char nameBuffer[64];
};

// start_routine函数现在是被几个线程执行呢? 10 
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
    // 演示打印!
    sleep(1);

    // 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
    ThreadData *td = static_cast<ThreadData*>(args);  // 安全的进行强制类型转换。
    // exit(-1);    // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
    // return nullptr;      // ok
    

    int cnt = 10;
    char tempStr[64] = {'\0'};
    while(cnt){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
        cout << tempStr;
        sleep(1);
    }

    pthread_exit((void*)td->number);  // oK
    // return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
} 

int main(){
    vector<ThreadData *> tids;
#define NUM 10
    // 一次性创建多个进程.
    for(int i = 1; i <= NUM; i++){
        // 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
        ThreadData* td = new ThreadData();
        td->number = i;
        snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
        pthread_create(&td->tid, nullptr, start_routine, td);
        // 每个进程进行保存!
        tids.push_back(td);
    }

    // 打印线程清单
    for(auto& e : tids){
        cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
    }

    // 等待所有的线程
    // for(auto& e : tids){
    //     int n = pthread_join(e->tid, (void **)&e->number);
    //     assert(n == 0);
    //     cout << "join:" << e->nameBuffer << " success!" << " number: " << (long long)e->number << endl;

    //     // 等待一个,就释放一个线程空概念
    //     delete e;
    // }
    // 取消线程
    // 线程如果是要被取消,退出码为-1(PTHREAD_CANCELED)
    // 5秒后取消一般的线程
    sleep(5);
    for(int i = 0; i<tids.size() / 2; i++){
        pthread_cancel(tids[i]->tid);
        cout << "pthread_cancel: " << tids[i]->number << " success!" << endl;
    }


    char tempStr[64] = {'\0'};
    while(true){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
        cout << tempStr;
        sleep(1);
    }
    return 0;
}

【8.4】Thread waiting

        Threads also need to wait. Why do threads need to wait?

        Threads also need to wait. If they don't wait, it will cause problems similar to zombie processes - memory leaks!

        The space of a thread that has exited has not been released and is still in the address space of the process. Creating a new thread will not reuse the address space of the thread that just exited.

// 功能:等待线程结束
// 原型
int pthread_join(pthread_t thread, void **value_ptr);
// 参数
// thread:线程ID
// value_ptr:它指向一个指针,后者指向线程的返回值,用来获取线程函数结束后,的执行见过
// 返回值:成功返回0;失败返回错误码

        The thread calling this function will hang and wait until the thread with id is terminated. Threads terminate in different ways, and the termination status obtained through pthread_join is different, summarized as follows:

  • If the thread thread returns through return, the unit pointed to by value_ptr stores the return value of the thread thread function.

  • If the thread thread is terminated abnormally by calling pthread_ cancel by another thread, the constant PTHREAD_ CANCELED is stored in the unit pointed by value_ ptr.

  • If the thread thread is terminated by calling pthread_exit itself, the unit pointed to by value_ptr stores the parameters passed to pthread_exit.

  • If you are not interested in the termination status of the thread thread, you can pass NULL to the value_ptr parameter.

// Makefile文件 》》》》》》》》》》》》》》》
myThread:myThread.cpp
    g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
    rm -f myThread  

// myThread.cpp文件》》》》》》》》》》》》》
// 监控脚本 
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;

/* 线程数据类 */
class ThreadData{
public:
    pthread_t tid;
    char nameBuffer[64];
};

// start_routine函数现在是被几个线程执行呢? 10 
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
    // 演示打印!
    sleep(1);

    // 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
    ThreadData *td = static_cast<ThreadData*>(args);  // 安全的进行强制类型转换。
    // exit(-1);    // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
    // return nullptr;      // ok
    

    int cnt = 10;
    char tempStr[64] = {'\0'};
    while(cnt){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
        cout << tempStr;
        sleep(1);
    }

    pthread_exit(nullptr);  // oK
    // return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
} 

int main(){
    vector<ThreadData *> tids;
#define NUM 10
    // 一次性创建多个进程.
    for(int i = 1; i <= NUM; i++){
        // 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
        ThreadData* td = new ThreadData();
        snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
        pthread_create(&td->tid, nullptr, start_routine, td);
        // 每个进程进行保存!
        tids.push_back(td);
    }

    // 打印线程清单
    for(auto& e : tids){
        cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
    }

    // 等待所有的线程
    for(auto& e : tids){
        int n = pthread_join(e->tid, nullptr);
        assert(n == 0);
        cout << "join:" << e->nameBuffer << " success!" << endl;

        // 等待一个,就释放一个线程空概念
        delete e;
    }

    char tempStr[64] = {'\0'};
    while(true){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
        cout << tempStr;
        sleep(1);
    }
    return 0;
}

[How to obtain thread custom exit code]

// 监控脚本 
// [shaxiang@VM-8-14-centos threads]$ while :; do ps -aL | head -1 && ps -aL | grep myThread; sleep 1; done
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;

/* 线程数据类 */
class ThreadData{
public:
    long long number;
    pthread_t tid;
    char nameBuffer[64];
};

// start_routine函数现在是被几个线程执行呢? 10 
// 这个函数现在是什么状态? 可重入状态
// 在函数内定义的变量,都叫做局部变量,具有临时性,依旧适用,在多线程状态下,其实每一个线程都有自己独立的栈结构!
void *start_routine(void *args){
    // 演示打印!
    sleep(1);

    // 一个线程如何出现了异常,会影响到其他线程吗?(会的,健壮性或者鲁棒性较差)
    ThreadData *td = static_cast<ThreadData*>(args);  // 安全的进行强制类型转换。
    // exit(-1);    // exit是不能终止线程的,因为exit是终止进程的,任何一个执行流调用exit都会让整个进程退出。
    // return nullptr;      // ok
    

    int cnt = 10;
    char tempStr[64] = {'\0'};
    while(cnt){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:%s cnt: %d\n", td->nameBuffer, cnt--);
        cout << tempStr;
        sleep(1);
    }

    pthread_exit((void*)td->number);  // oK
    // return nullptr; // 线程函数结束,在函数return的时候线程就终止了。
} 

int main(){
    vector<ThreadData *> tids;
#define NUM 10
    // 一次性创建多个进程.
    for(int i = 1; i <= NUM; i++){
        // 这里没创建一个线程都会有一份独立的数据-> td传递的是指针.
        ThreadData* td = new ThreadData();
        td->number = i;
        snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s,%d", "thread", i);
        pthread_create(&td->tid, nullptr, start_routine, td);
        // 每个进程进行保存!
        tids.push_back(td);
    }

    // 打印线程清单
    for(auto& e : tids){
        cout << "create thread:" << e->nameBuffer << " " << e->tid << "success" << endl;
    }

    // 等待所有的线程
    for(auto& e : tids){
        int n = pthread_join(e->tid, (void **)&e->number);
        assert(n == 0);
        cout << "join:" << e->nameBuffer << " success!" << " number: " << (long long)e->number << endl;

        // 等待一个,就释放一个线程空概念
        delete e;
    }

    char tempStr[64] = {'\0'};
    while(true){
        snprintf(tempStr, sizeof(tempStr), "new thread create success,name:main tread\n");
        cout << tempStr;
        sleep(1);
    }
    return 0;
}

[Question] Why don’t I see the corresponding exit signal when the thread exits?

        When a thread exits and receives a signal, the entire process will exit. pthread_join: By default, it is assumed that the function call will be successful, regardless of exceptions. Exceptions are issues that your process considers.

【8.5】Separate threads

        By default, newly created threads are joinable. After the thread exits, it needs to perform a pthread_join operation, otherwise resources cannot be released, causing system leaks.

        If you don't care about the return value of the thread, join is a burden. At this time, we can tell the system to automatically release the thread resources when the thread exits.

[Introduction of functions]

#include <pthread.h>
// 功能:获取线程id
// 原型
pthread_t pthread_self(void);

【Code example】

// Makefile文件 》》》》》》》》》》》》》》》》》》》》》》
myThread:myThread.cpp
    g++ -o $@ $^ -std=c++11 -l pthread

.PHONY:clean
clean:
    rm -f myThread

// myThread.cpp文件》》》》》》》》》》》》》》》》》》》
#include <iostream>
#include <cassert>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using std::cout;
using std::cin;
using std::endl;

std::string change_id(const pthread_t &thread_id){
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}

// 线程方法调用.
void *start_routine(void *args){
    sleep(1);
    std::string threadName = static_cast<const char*>(args);
    while(true){
        cout << threadName << " running.... : " << change_id(pthread_self()) << endl;
        sleep(1);
    }
}

int main(){
    // 创建线程.
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread One");
    assert(n == 0); (void)n;

    // 打印创建的pthread_t的线程id只
    cout << "main thread id : " << change_id(pthread_self()) << endl;
    cout << "main thread running ... new thread id: " <<  change_id(tid) << endl;

    // 等待线程.
    pthread_join(tid, nullptr);

    return 0;
}

[shaxiang@VM-8-14-centos threads_02]$ ./myThread 
main thread id : 0xaf078740
main thread running ... new thread id: 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700
thread One running.... : 0xadfe3700

[It can be other threads in the thread group that separate the target thread, or the thread itself can separate]

#include <pthread.h>
// 功能:线程分离
// 原型
int pthread_detach(pthread_t thread);

【Code example】

#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
using std::cout;
using std::cin;
using std::endl;

std::string change_id(const pthread_t &thread_id){
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}

// 线程方法调用.
void *start_routine(void *args){
    sleep(1);
    std::string threadName = static_cast<const char*>(args);

    int cnt = 5;
    while(cnt--){
        cout << threadName << " running.... : " << change_id(pthread_self()) << endl;
        sleep(1);
    }

}

int main(){
    // 创建线程.
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread One");
    assert(n == 0); (void)n;
    // 将线程进行分离
    pthread_detach(tid);

    // 打印创建的pthread_t的线程id只
    cout << "main thread id : " << change_id(pthread_self()) << endl;
    cout << "main thread running ... new thread id: " <<  change_id(tid) << endl;

    // 等待线程 -> 如果线程默认是joinable的,如果设置了分离状态,不能够进行等待了!
    int ret = pthread_join(tid, nullptr);
    cout << "result: " << ret << " : " << strerror(ret) << endl;    // ret失败了!

    return 0;
}

// 打印结果       
[shaxiang@VM-8-14-centos threads_02]$ ./myThread 
main thread id : 0xac09b740
main thread running ... new thread id: 0xab006700
result: 22 : Invalid argument     // 非法参数,因为线程被分离了!

【9】Thread ID and process address space layout

        The pthread_create function generates a thread ID and stores it in the address pointed to by the first parameter. This thread ID is not the same as the thread ID mentioned earlier.

        The thread ID mentioned earlier belongs to the category of process scheduling. Because a thread is a lightweight process and is the smallest unit of the operating system scheduler, a numerical value is needed to uniquely represent the thread.

        The first parameter of the pthread_create function points to a virtual memory unit. The address of the memory unit is the thread ID of the newly created thread, which belongs to the category of the NPTL thread library. Subsequent operations of the thread library operate threads based on the thread ID.

        The thread library NPTL provides the pthread_self function to obtain the thread's own ID:

#include <pthread.h>
// 功能:获取线程id
// 原型
pthread_t pthread_self(void);

        What type is pthread_t? Depends on implementation. For the current NPTL implementation in Linux, the thread ID of the pthread_t type is essentially an address in the address space of a process.

[Thread local storage]

[Local storage test int g_value = 100]

// Makefile文件 》》》》》》》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11

myThread:Thread.cc 
	$(cc) -o $@ $^ $(standard) -l pthread

.PHONY:clean
clean:
	rm -rf myThread
	
// Thread.cc文件》》》》》》》》》》》》》》》》》》》	
#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

int g_value = 100;

string ChangeId(const pthread_t& tid) {
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}

void* StartRoutine(void* args) {
    sleep(2);
    const char* msg = static_cast<const char*>(args);

    while(true) {
        cout << msg << " Thread Id: " << ChangeId(pthread_self())\
            << "g_value: " << g_value << " &g_value: " << &g_value << endl;
        g_value++;
        sleep(1);
    }

    pthread_exit(nullptr);
}

int main() {
    // 创建线程,并且将线程分离
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, StartRoutine, (void*)"One");
    assert(n == 0); (void)n;

    // 打印创建的pthread_t的线程id
    while(true) {
        cout << "Main Thread Id: " << ChangeId(pthread_self())\
            << "g_value: " << g_value << " &g_value: " << &g_value << endl;
        sleep(1);
    }
    
    return 0;
}

[Local storage test__threadint g_value = 100]

// Makefile文件 》》》》》》》》》》》》》》》》》》》》》》
cc=g++
standard=-std=c++11

myThread:Thread.cc 
	$(cc) -o $@ $^ $(standard) -l pthread

.PHONY:clean
clean:
	rm -rf myThread
	
// Thread.cc文件》》》》》》》》》》》》》》》》》》》
#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

__thread int g_value = 100;

string ChangeId(const pthread_t& tid) {
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}

void* StartRoutine(void* args) {
    sleep(2);
    const char* msg = static_cast<const char*>(args);

    while(true) {
        cout << msg << " Thread Id: " << ChangeId(pthread_self())\
            << "g_value: " << g_value << " &g_value: " << &g_value << endl;
        g_value++;
        sleep(1);
    }

    pthread_exit(nullptr);
}

int main() {
    // 创建线程,并且将线程分离
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, StartRoutine, (void*)"One");
    assert(n == 0); (void)n;

    // 打印创建的pthread_t的线程id
    while(true) {
        cout << "Main Thread Id: " << ChangeId(pthread_self())\
            << "g_value: " << g_value << " &g_value: " << &g_value << endl;
        sleep(1);
    }
    
    return 0;
}

【10】Encapsulating thread classes based on native thread library

【Makefile】

# 定义变量给变量复制对应的字符串标签
cc:= g++
standrad:= -std=c++11 

# 定义编译链接关系
myThreadBase: ThreadBase.cc
	$(cc) -o $@ $^ $(standard) -lpthread

# 定义命令
clean:
	rm -rf myThreadBase

.PHONY: clean

【Thread.hpp file】

#pragma once 
#include <cstdio>
#include <cassert>
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

namespace Thread
{
    class ThreadBase;

    /* 线程上下文数据封装类 */
    class ThreadBaseConnectText
    {
    public:
        /* - 构造函数(无参)
         */
        ThreadBaseConnectText()
            : _this(nullptr)
            , _args(nullptr)
        {}

        /* - 析构函数
         */
        ~ThreadBaseConnectText() 
        {}

    public:
        ThreadBase* _this;
        void*       _args;
    };

    /* 基于原生线程库的线程封装类 */
    class ThreadBase
    {
    private:
        // 仿函数类型
        using func_t = std::function<void*(void*)>;
    
    private:
        const int ctNum = 64;


    public:
        /* - 构造函数(无参)
         * - func:回调函数
         * - args:回调函数参数
         * - number:线程编号用于构建线程名称
         */
        ThreadBase(func_t func = nullptr, void* args = nullptr, const int& number = 1)
            : _threadFunc(func)
            , _threadArgs(args)
        {
            // 创建线程名
            char threadName[ctNum];
            snprintf(threadName, sizeof(threadName), "Thread-[%d]", number);
            _threadName = threadName;

            // 创建线程上下文(需要手写内存释放->[0-1])
            ThreadBaseConnectText* connectext = new ThreadBaseConnectText();
            connectext->_this = this;
            connectext->_args = _threadArgs;

            // 创建线程Id
            int state = pthread_create(&_threadId, nullptr, StartRoutine, (void*)connectext);
            assert(state == 0); (void)state;
        }

        /* - 析构函数
         */
        ~ThreadBase()
        {}
        
    public:
        /* - 线程等待
         */
        void Join()
        {
            // 等待线程
            int state = pthread_join(_threadId, nullptr);
            assert(state == 0); (void)state;
        }

    public:
        /* - 获取线程名称
         */        
        std::string GetThreadName()
        {
            return _threadName;
        }

        /* - 获取线程Id
         */  
        std::string GetThreadId()
        {
            char buffer[ctNum];
            snprintf(buffer, sizeof(buffer), "0x%x", _threadId);
            return buffer;
        }

    public:
        /* - 线程函数(静态函数没有this指针)
         */
        static void* StartRoutine(void* args)
        {
            // 获取线程链接上下文
            ThreadBaseConnectText* connecText = static_cast<ThreadBaseConnectText*>(args);
            void* threadRet = connecText->_this->Run(connecText->_args);

            // 内存释放->[0-1]
            delete connecText;
            return threadRet;
        }

    private:
        /* - StartRoutine专用函数(因为C/C++混编的原因)
         */
        void* Run(void* args)
        {
            // 调用线程回调函数
            return _threadFunc(args);
        }

    private:
        std::string     _threadName;        // 线程名称
        pthread_t       _threadId;          // 线程Id
        func_t          _threadFunc;        // 线程回调函数
        void*           _threadArgs;        // 线程参数
    };
}

【Thread.cc file】

#include <iostream>
#include <memory>
#include "unistd.h"
#include "ThreadBase.hpp"

using namespace std;
using namespace Thread;

/* 线程函数 */
void* StartRoutine(void* args)
{
    // 获取线程链接上下文
    std::string threadWork = static_cast<const char*>(args);
    while(true)
    {
        sleep(1);

        char buffer[64];
        snprintf(buffer, sizeof(buffer), "0x%x", pthread_self());
        std::cout << "Id-[" << buffer << "]: " << threadWork << std::endl;
    }
    return nullptr;
}

/* 程序入口函数 */
int main()
{
    // 创建第一个线程:做网络工作
    std::unique_ptr<ThreadBase> pTd1(new ThreadBase(StartRoutine, (void*)"网络线程工作中...", 1));
    std::cout << pTd1->GetThreadName() + " Success... Id-[" << pTd1->GetThreadId() << "]..." << std::endl;;
    // 创建第二个线程:做文件工作
    std::unique_ptr<ThreadBase> pTd2(new ThreadBase(StartRoutine, (void*)"文件线程工作中...", 2));
    std::cout << pTd2->GetThreadName() + " Success... Id-[" << pTd2->GetThreadId() << "]..." << std::endl;;
    // 创建第三个线程:做视频工作
    std::unique_ptr<ThreadBase> pTd3(new ThreadBase(StartRoutine, (void*)"视频线程工作中...", 3));
    std::cout << pTd3->GetThreadName() + " Success... Id-[" << pTd3->GetThreadId() << "]..." << std::endl;;

    // 线程等待
    pTd1->Join();
    pTd2->Join();
    pTd3->Join();
    return 0;
}

【Print result】

[shaxiang@VM-8-14-centos ThreadBase]$ ./myThreadBase 
Thread-[1] Success... Id-[0xdc142700]...
Thread-[2] Success... Id-[0xdb941700]...
Thread-[3] Success... Id-[0xdb140700]...
Id-[0xdc142700]: 网络线程工作中...
Id-[0xdb941700]: 文件线程工作中...
Id-[0xdb140700]: 视频线程工作中...
Id-[0xdc142700]: 网络线程工作中...
Id-[0xdb941700]: 文件线程工作中...
Id-[0xdb140700]: 视频线程工作中...

Guess you like

Origin blog.csdn.net/lx473774000/article/details/132867357