【Linux】线程创建&&等待&&终止&&分离

🌻个人主页:路飞雪吖~

🌠专栏:Linux


目录

一、Linux线程控制

✨POSIX线程库

✨创建线程

✨线程等待

🍔小贴士:

✨线程终止

✨分离线程


一、Linux线程控制

✨POSIX线程库

• 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"打头的;

• 要使用这些函数库,要通过引入头文件<pthread.h>;

• 链接这些线程函数库时要使用编译器命令的 "-lpthread" 选项;

✨创建线程

在Linux内核中,没有线程的概念【没有单独设计tcb,只有进程pcb】!只有LWP,轻量级进程的概念,线程是使用LWP模拟的!这就意味着,Linux操作系统,不会给我们提供线程接口,只会提供创建轻量级进程的接口!

cpp 复制代码
功能:创建⼀个新的线程
原型:
 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;失败返回错误码

错误检查:

• 传统的一些函数,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误;

• pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)而是将错误代码通过返回值返回;

• pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小;

为了服务上层用户,在操作系统和用户之间封装一层软件层 【计算机当中任何问题,都可以新增一层软件层来完成】,封装一层软件层:把 clone() 系统调用封装成线程创建的接口,此时就可以使用库,库来完成所有线程的创建未来的管理,此时用户就不用关系 操作系统对于LWP的概念,只需要用线程相关的概念来进行上层代码的编写---- 用户级线程

Window有真正的TCB,就能提供系统级别的进程和线程的创建接口。

<pthread.h> 用户级别的线程库【独立的库】,Linux系统自带的!原生线程库!

bash 复制代码
// Makefile

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f mythread
cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include<pthread.h>

void *routine(void *args)
{
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        std::cout << "我是新线程,我的名字是:" << name << std::endl;
        sleep(1);
    }
    return 0;
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    if(n != 0)// 线程创建失败
    {
        std::cout << "create thread error:" << strerror(n) << std::endl;
        return 1;
    }

    while(true)
    {
        std::cout << "我是main线程..." << std::endl;
        sleep(1);
    }  
}

C++支持多线程,本质就是封装了pthread库!

cpp 复制代码
//C++ 支持多线程

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <thread> // C++11 线程库

int main()
{
    // 新线程
    std::thread t([](){
         while(true)
    {
        std::cout << "我是新线程,我的名字是:new thread" << std::endl;
        sleep(1);
    }
    });

    // 主线程
    while(true)
    {
        std::cout << "我是main线程..." << std::endl;
        sleep(1);
    }  
    return 0;
}

其他高级语言也支持多线程,一定是 pthread 库,系统调用和库函数之间的关系,在库里面进行封装,保证代码的跨平台性。


🌠获得自己对应的线程id:

• 打印出来的 tid 是通过 pthread 库中有函数 pthread_self() 得到的,它返回⼀个 pthread_t 类型的

变量,指代的是调用 pthread_self() 函数的线程的 "ID"。

• 怎么理解这个"ID"呢?这个"ID"是 pthread 库给每个线程定义的进程内唯⼀标识,是 pthread 库
维持的。

• 由于每个进程有自己独立的内存空间,故此**"ID"的作用域是进程级而非系统级(内核不认识)**。

• 其实 pthread 库 也是通过内核提供的系统调用(例如clone)来创建线程的,而内核会为每个线程创建系统全局唯一的"ID"来唯一标识这个线程。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

std::string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

void *routine(void *args)
{
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) << std::endl;
        sleep(1);
    }
    return 0;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid: 0x%lx\n", tid);

    while(true)
    {
        std::cout << "我是main线程..." << std::endl;
        sleep(1);
    }
}

• 线程tid,主要是能够区分对应的线程,线程具有唯一性;

• 1.新线程和main线程谁先运行,不确定【不同平台】

• 2.线程创建出来,要对进程的时间片进行瓜分

🌠小贴士:

使用PS命令查看线程信息

运行代码后执行:

•****-L 选项:打印线程信息

• LWP 是什么呢?LWP 得到的是真正的线程ID。之前使用 pthread_self 得到的这个数实际上是一个地址,在虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线 程ID,线程栈,寄存器等属性。

**•**在 ps -aL 得到的线程ID,有一个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟 地址空间的栈上,而其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库 提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。


🌠一个进程内有多个执行流,多个线程执行同一个函数:这个函数被重入了!!

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

std::string toHex(pthread_t tid)
{
    // 4.进程内的函数,线程共享
    char buffer[64];// buffer在栈上开辟
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

// 被重入了!!!
void *routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) << std::endl;
        sleep(1);
    }
    return 0;
}

int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    pthread_t tid1;
    pthread_create(&tid1, nullptr, routine, (void *)"thread-1");
    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid: 0x%lx\n", tid1);

    pthread_t tid2;
    pthread_create(&tid2, nullptr, routine, (void *)"thread-2");
    printf("new thread tid: 0x%lx\n", tid2);

    pthread_t tid3;
    pthread_create(&tid3, nullptr, routine, (void *)"thread-3");
    printf("new thread tid: 0x%lx\n", tid3);

    pthread_t tid4;
    pthread_create(&tid4, nullptr, routine, (void *)"thread-4");
    printf("new thread tid: 0x%lx\n", tid4);

    while (true)
    {
        std::cout << "我是main线程..." << std::endl;
        sleep(1);
    }
}

多线程中,每一个线程都可以对向通一个显示器【文件】进行打印 ,前提条件是,所有线程都能看到同一个显示器文件【显示器文件 就相当于被当作一个公共资源】,多线程访问公共资源,本质上就是多线程在写入文件【各个线程自己写自己的】,而这个公共资源没有被保护,没有被保护的公共资源 被多线程访问,而产生打印错乱,即为数据不一致问题;因而代码是有并发问题的!

• 3. 不加保护的情况下,显示器文件是共享资源!

• 4. 进程内的函数,线程共享;

• 线程都能看到,整个进程的任意一个方法【toHex()】,每个函数都要形成栈帧,栈帧结构都在自己的栈上。


一个线程只要把全局变量改了,另一个线程就能看到被修改的结果和变化。

• 5. 全局变量在线程内部是共享的;

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}


void *routine1(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;
        gval++;
        sleep(1);
    }
    return 0;
}

void *routine2(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(只检测):" << gval << std::endl;
        sleep(1);
    }
    return 0;
}

int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    pthread_t tid1;
    pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");
    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid: 0x%lx\n", tid1);

    pthread_t tid2;
    pthread_create(&tid2, nullptr, routine2, (void *)"thread-2");
    printf("new thread tid: 0x%lx\n", tid2);

    // pthread_t tid3;
    // pthread_create(&tid3, nullptr, routine, (void *)"thread-3");
    // printf("new thread tid: 0x%lx\n", tid3);

    // pthread_t tid4;
    // pthread_create(&tid4, nullptr, routine, (void *)"thread-4");
    // printf("new thread tid: 0x%lx\n", tid4);

    while (true)
    {
        std::cout << "我是main线程..." << std::endl;
        sleep(1);
    }
}

【打印的结果不稳定,是进程调度产生的】

在多线程代码当中我们想让多个线程看到同一份资源是非常容易的,只要定义全局变量就可以了。


让线程2【routine2】崩溃,观察会发生什么现象:6. 一旦任何一个线程出现崩溃,会导致其他线程,包括主线程在内,全部都会退出。

多线程的弊端:一旦有一个线程崩溃,其他的线程就会全部的崩溃。

• 任何一个线程属于进程的一个执行分支,所以线程做任何事就是进程在做;

一旦某一个进程出现野指针,即查页表查失败 --> CPU内部的MMU直接报错 --> CPU内部触发软中断,OS中的全部的功能全部停下来,执行中断处理方法,根据软中断的中断号,直接查中断向量表,执行异常处理 --> 给目标进程发信号

6.1 异常的本质是信号,进程的多个线程是共享的,所以一旦来了一个异常信号,当前的OS会给每一个线程 设置异常处理。【信号是给进程的,进程中的每一个线程都会收到信号】

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}


void *routine1(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;
        gval++;
        sleep(1);
    }
    return 0;
}

void *routine2(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(只检测):" << gval << std::endl;
        sleep(1);
        // 6. 线程一旦出现问题,可能会导致其他线程其他线程全部崩溃
        // 6.1 异常的本质是信号
        int *p = nullptr;// 查页表失败 --> CPU内部的MMU报错 --> CPU触发软中断
        *p = 100;
    }
    return 0;
}

int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    pthread_t tid1;
    pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");
    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid1: 0x%lx\n", tid1);

    pthread_t tid2;
    pthread_create(&tid2, nullptr, routine2, (void *)"thread-2");
    printf("new thread tid2: 0x%lx\n", tid2);

    // pthread_t tid3;
    // pthread_create(&tid3, nullptr, routine, (void *)"thread-3");
    // printf("new thread tid: 0x%lx\n", tid3);

    // pthread_t tid4;
    // pthread_create(&tid4, nullptr, routine, (void *)"thread-4");
    // printf("new thread tid: 0x%lx\n", tid4);

    while (true)
    {
        std::cout << "我是main线程..." << std::endl;
        sleep(1);
    }
}

一个进程内有多个线程,当有一个线程触发异常,一个进程里面全部的线程都会触发异常,如何知道这些线程是同一个进程里面的呢?pid 只有唯一性标识,不能很快的去查找【遍历所有进程的链表,效率太低了】,那该怎么办呢?

进程之间的关系:父子关系,兄弟关系,组关系 。进程PCB里面有一张双链表,可以维护组关系,可以把进程里所有的LWP作为一个组,单独用一个小的链表去维护起来,因为任何一个PCB既可以属于调度队列,又可以属于等待队列,还可以属于其他数据结构。

✨线程等待

线程创建之后,谁先运行不确定,一般要保证主线程最后退出;进程创建之后,父子谁先运行不确定,一般要保证父进程最后退出,线程是主进程创建的。进程状态就是线程状态。

7. 线程创建之后,也是要被等待和回收的。

理由:

a. 等待原因【必要】:类似僵尸进程的问题【在系统层面,线程退出,这个线程的task_struct不敢随意释放;用户级线程 在线程库里面也要申请很多资源 不等待会造成内存泄漏的问题】

b. 收原因【可选】:为了知道新线程的执行结果

调用该函数的线程将挂起等待,直到 id 为 thread 的线程终中。thread 线程以不同的方法终止,通过 pthread_join 得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ptr 所指向的单元里存放的是thread线程函数的返回值。

  2. 如果thread线程被别的线程调用 pthread_cancel 异常终掉, value_ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED【宏】。

  3. 如果thread线程是自己调用 pthread_exit 终止的, value_ptr 所指向的单元存放的是传给 pthread_exit 的参数。

  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给 value_ptr参数。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}


void *routine1(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;
        gval++;
        sleep(1);
    }
    return 0;
}

int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    pthread_t tid1;
    pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");
    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid1: 0x%lx\n", tid1);

    // 7. 线程创建之后,也是要被等待和回收的!
    // 7.1 理由:a. 类似僵尸进程的问题
    //          b. 为了知道新线程的执行结果
    int n = pthread_join(tid1, nullptr);// 等待线程
    if(n != 0)
    {
        std::cout << "join error:" << n << "," << strerror(n) << std::endl;
        return 1;
    }
    std::cout << "join success!" << std::endl;

   
    while (true)
    {
        std::cout << "我是main线程..." << std::endl;
        sleep(1);
    }
}

新线程一直不退,主线程就pthread_join()一直阻塞等待:

让新线程阻塞1s后,执行break:线程等待成功!

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}


void *routine1(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;
        gval++;
        sleep(1);
        break;
    }
    return 0;
}


int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    pthread_t tid1;
    pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");// 创建线程
    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid1: 0x%lx\n", tid1);

    // 7. 线程创建之后,也是要被等待和回收的!
    // 7.1 理由:a. 类似僵尸进程的问题
    //          b. 为了知道新线程的执行结果
    int n = pthread_join(tid1, nullptr);// 线程等待
    if(n != 0)
    {
        std::cout << "join error:" << n << "," << strerror(n) << std::endl;
        return 1;
    }
    std::cout << "join success!" << std::endl;

}

线程等待错误:pthread_join等待错误的线程tid。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}


void *routine1(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;
        gval++;
        sleep(1);
        break;
    }
    return 0;
}


int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    pthread_t tid1;
    pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");// 创建线程
    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid1: 0x%lx\n", tid1);

    // 7. 线程创建之后,也是要被等待和回收的!
    // 7.1 理由:a. 类似僵尸进程的问题
    //          b. 为了知道新线程的执行结果
    //int n = pthread_join(tid1, nullptr);// 线程等待
    int n = pthread_join(pthread_self(), nullptr);// 等待错误,等待的不是所产生的线程tid,而是自己本身的ID
    if(n != 0)
    {
        std::cout << "join error:" << n << "," << strerror(n) << std::endl;
        return 1;
    }
    std::cout << "join success!" << std::endl;

}

pthread_join这个线程若一直不退,主线程就会一直阻塞等待。

pthread_join默认是阻塞式的,让主线程阻塞时等待。


8. 线程传参问题:传递参数,可以是变量、数字、对象、结构体、类....

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

class ThreadData
{
public:
    ThreadData(const std::string &name, int a, int b):_name(name),_a(a),_b(b)
    {}

    int Excute()
    {
        return _a + _b;
    }

    std::string Name(){return _name;}

    ~ThreadData()
    {}
private:
    std::string _name;
    int _a;
    int _b;
};

// 5. 全局变量在线程内部是共享的
int gval = 100; // 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

// 被重入了!
void *routine1(void *args)
{
    // std::string name = static_cast<const char *>(args);
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;
        gval++;
        std::cout << "task result is :" << td->Excute() << std::endl;
        sleep(1);
        break;
    }
    return 0;
}

int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    // 8. 传参问题:传递参数,可以是变量、数字、对象
    pthread_t tid1;
    ThreadData *td = new ThreadData("thread-1", 10, 20);
    pthread_create(&tid1, nullptr, routine1, td); // 创建线程
    
    printf("new thread tid1: 0x%lx\n", tid1);

    // 7. 线程创建之后,也是要被等待和回收的!
    // 7.1 理由:a. 类似僵尸进程的问题
    //          b. 为了知道新线程的执行结果
    int n = pthread_join(tid1, nullptr);// 线程等待
    // int n = pthread_join(pthread_self(), nullptr); // 等待错误,等待的不是所产生的线程tid,而是自己本身的ID
    if (n != 0)
    {
        std::cout << "join error:" << n << "," << strerror(n) << std::endl;
        return 1;
    }
    std::cout << "join success!" << std::endl;
}
🍔小贴士:
cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

class ThreadData
{
public:
    ThreadData(const std::string &name, int a, int b):_name(name),_a(a),_b(b)
    {}

    int Excute()
    {
        return _a + _b;
    }

    std::string Name(){return _name;}

    ~ThreadData()
    {}
private:
    std::string _name;
    int _a;
    int _b;
};

// 5. 全局变量在线程内部是共享的
int gval = 100; // 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

// 被重入了!
void *routine1(void *args)
{
    // std::string name = static_cast<const char *>(args);
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;
        gval++;
        std::cout << "task result is :" << td->Excute() << std::endl;
        sleep(1);
        break;
    }
    // return 0;
    return (void*)10;// 线程退出方式:1、线程入口函数retur    n,表示线程退出
}

int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    // 8. 传参问题:传递参数,可以是变量、数字、对象
    pthread_t tid1;
    ThreadData *td = new ThreadData("thread-1", 10, 20);
    pthread_create(&tid1, nullptr, routine1, td); // 创建线程
    
    // pthread_create(&tid1, nullptr, routine1, (void *)"thread-1"); // 创建线程

    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid1: 0x%lx\n", tid1);

    // 7. 线程创建之后,也是要被等待和回收的!
    // 7.1 理由:a. 类似僵尸进程的问题
    //          b. 为了知道新线程的执行结果

    void *ret = nullptr;// 线程所对应的返回值【线程routine1的返回值(void*)10】
    int n = pthread_join(tid1, &ret);// 线程等待
    // int n = pthread_join(pthread_self(), nullptr); // 等待错误,等待的不是所产生的线程tid,而是自己本身的ID
    if (n != 0)
    {
        std::cout << "join error:" << n << "," << strerror(n) << std::endl;
        return 1;
        
    }
    std::cout << "join success!, ret: " << (long long int)ret << std::endl;

}

理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!

• 传参问题:传递参数,可以是变量、数字、对象

• 返回值问题:返回参数,可以是变量、数字、对象

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

class ThreadData
{
public:
    ThreadData(const std::string &name, int a, int b):_name(name),_a(a),_b(b)
    {}

    void Excute()
    {
        _result = _a + _b;
    }

    int Result(){return _result;}

    std::string Name(){return _name;}

    ~ThreadData()
    {}
private:
    std::string _name;
    int _a;
    int _b;
    int _result;
};

// 5. 全局变量在线程内部是共享的
int gval = 100; // 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

// 被重入了!
void *routine1(void *args)
{
    // std::string name = static_cast<const char *>(args);
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;
        gval++;
        td->Excute();
        // std::cout << "task result is :" << td->Excute() << std::endl;
        sleep(1);
        break;
    }
    
    return td;
}

int main()
{
    // 1. 新线程和main线程谁先运行,不确定
    // 2. 线程创建出来,要对进程的时间片进行瓜分
    // 8. 传参问题:传递参数,可以是变量、数字、对象
    pthread_t tid1;
    ThreadData *td = new ThreadData("thread-1", 10, 20);// 主线程申请的堆空间
    pthread_create(&tid1, nullptr, routine1, td); // 创建线程
    
    // pthread_create(&tid1, nullptr, routine1, (void *)"thread-1"); // 创建线程

    // std::cout << "new thread tid:" << tid << std::endl;
    printf("new thread tid1: 0x%lx\n", tid1);

    // 7. 线程创建之后,也是要被等待和回收的!
    // 7.1 理由:a. 类似僵尸进程的问题
    //          b. 为了知道新线程的执行结果

    // void *ret = nullptr;// 线程所对应的返回值【线程routine1的返回值(void*)10】
    ThreadData *rtd = nullptr;
    int n = pthread_join(tid1, (void**)&rtd);// 我们可以保证,执行完毕,任务一定处理完了,结果变量一定已经被写入了!
    // int n = pthread_join(pthread_self(), nullptr); // 等待错误,等待的不是所产生的线程tid,而是自己本身的ID
    if (n != 0)
    {
        std::cout << "join error:" << n << "," << strerror(n) << std::endl;
        return 1;
        
    }
    std::cout << "join success!, ret: " << rtd->Result() << std::endl;
    delete td;
}

🌠创建多线程与等待,如何把任务进行处理:

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

class ThreadData
{
public:
    ThreadData()
    {}

    void Init(const std::string &name, int a, int b)
    {
        _name = name;
        _a = a;
        _b = b;
    }

    void Excute()
    {
        _result = _a + _b;
    }

    int Result(){ return _result; }

    std::string Name(){ return _name; }

    void SetID(pthread_t tid) { _tid = tid; }

    pthread_t ID() { return _tid; }

    int A() { return _a; }

    int B() { return _b; }

    ~ThreadData()
    {}
private:
    std::string _name;
    int _a;
    int _b;
    int _result;
    pthread_t _tid;
};

// 5. 全局变量在线程内部是共享的
int gval = 100; // 被所有线程共享【在全局数据区】

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

// 被重入了!
void *routine1(void *args)
{
    // std::string name = static_cast<const char *>(args);
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        // 3. 不加保护的情况下,显示器文件就是共享资源!
        std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;
        gval++;
        td->Excute();
        // std::cout << "task result is :" << td->Excute() << std::endl;
        sleep(1);
        break;
    }
    // return 0;
    // 8. 返回值问题:返回参数,可以是变量、数字、对象!
    // 8.1 理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!
    // return (void*)10;// 线程退出方式:1、线程入口函数retur    n,表示线程退出

    return td;
}

#define NUM 10

int main()
{
    ThreadData td[NUM];
    // 准备我们要加工处理的数据
    for(int i = 0; i < NUM; i++)
    {
        char id[64];
        td[i].Init(id, i*10, i*20);
    }

    // 创建多线程
    for(int i = 0; i < NUM; i++)
    {
        pthread_t id;
        pthread_create(&id, nullptr, routine1, &td[i]);
        td[i].SetID(id);
    }

    // 等待多线程
    for(int i = 0; i < NUM; i++)
    {
        pthread_join(td[i].ID(), nullptr);
    }

    // 汇总处理结果
    for(int i = 0; i <NUM; i++)
    {
        printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].ID());
    }
}

在多线程当中所有的东西都是共享的,线程有独立的栈,所以线程里面的局部变量,不能被其他线程看到;但是想要被看到也是有方法的。

在线程的代码里,不同线程定义的临时变量,是在不同的栈上的。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

int *addr = nullptr;// 把线程内部的地址传出去,其他线程就可以看到

void* start1(void *args)
{
    std::string name = static_cast<const char*>(args);
    int a = 100;
    addr = &a;
    while (true)
    {
        std::cout << name << "local val a : " << a << std::endl;
        sleep(1);
    } 
}

void* start2(void *args)
{
    std::string name = static_cast<const char*>(args);
    while (true)
    {
        if(addr != nullptr)
            std::cout << name << "local val a : " << (*addr)++ << std::endl;
        sleep(1);
    } 
}

int main()
{
    pthread_t tid1, tid2;
    // 创建进程
    pthread_create(&tid1, nullptr, start1, (void*)"thread-1");
    pthread_create(&tid2, nullptr, start2, (void*)"thread-2");

    //进程等待
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
}

✨线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

1、从线程函数 return。这种方法对主线程不适用,从main函数return相当于调用exit。

2、 线程可以调用 pthread_exit 终止自己。

功能:线程终止

原型: void pthread_exit(void *value_ptr);

参数: value_ptr:value_ptr不要指向⼀个局部变量。

返回值: ⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)

3、一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程【取消线程,一定是目标线程已经启动了】

功能:取消⼀个执⾏中的线程

原型: int pthread_cancel(pthread_t thread);

参数: thread:线程ID

返回值:成功返回0;失败返回错误码

**•**9. 新线程return,表示该线程退出;主线程return,表示进程结束!

• 任何地方调用exit,表示进程退出!

**•**pthread_exit() <==> return

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

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

void *start(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // std::cout << name << "local val a : " << (*addr)++ << std::endl;
        sleep(1);
        break;
    }
    // return 0;// 9. 新线程return,表示该线程退出
    // exit(1);// 任何地方调用exit,表示进程退出!
    pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    // 创建进程
    pthread_create(&tid, nullptr, start, (void *)"thread-1");
    // 进程等待
    void *ret = nullptr;
    pthread_join(tid, &ret);
    std::cout << "new tjread exit code: " << (long long int)ret << std::endl;
    
    return 0;// 主线程return,表示进程结束! 
}

pthread_cancel 在写多线程代码的时候,有些线程不想要了可以取消掉;但是并不建议使用这个函数,因为在取消的时候目标线程是什么工作状态我们并不清楚。

new tjread exit code: -1 :说明我们取消一个线程,我们依旧要 pthread_join() ,线程退出 若不等待,就会变成类似僵尸进程的问题。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

void *start(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        // std::cout << name << "local val a : " << (*addr)++ << std::endl;
        std::cout << "I am a new thread" << std::endl;
        sleep(1);
        // break;
    }
    // return 0;// 9. 新线程return,表示该线程退出
    // exit(1);// 任何地方调用exit,表示进程退出!
    pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    // 创建进程
    pthread_create(&tid, nullptr, start, (void *)"thread-1");

    sleep(5);

    pthread_cancel(tid);
    std::cout << "取消线程:" << tid << std::endl;

    sleep(5);

    // 进程等待
    void *ret = nullptr;
    pthread_join(tid, &ret); // PTHREAD_CANCELED;
    std::cout << "new tjread exit code: " << (long long int)ret << std::endl;
    
    return 0;// 主线程return,表示进程结束! 
}

✨分离线程

我主线程也要做自己的事情呢?

可以不等待新线程 --- 将目标线程设置为分离状态!

线程被等待状态:

1. pthread_join():线程需要被join(默认)

2. pthread_detach:线程分离(主线程不需要等待新线程【类似分家】)

注意:在多执行流情况下,主执行流是最后退出的!

线程一旦被分离,就不能 pthread_join() 。

• 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏。

• 如果不关心线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

void *start(void *args)
{
    // pthread_detach(pthread_self()); // 自己把自己分离,主线程 pthread_join()失败,进程不会阻塞,直接return退出
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        std::cout << "I am a new thread" << std::endl;
        sleep(1);
        // break;
    }
   
    pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    // 创建进程
    pthread_create(&tid, nullptr, start, (void *)"thread-1");
    // pthread_detach(tid); // 把目标线程进行分离,pthread_join() 也是失败的,进程不会阻塞,直接return退出

    sleep(5);

    // 进程等待
    void *ret = nullptr;
    int n = pthread_join(tid, &ret); // PTHREAD_CANCELED;
    std::cout << "new tjread exit code: " << (long long int)ret << ", n: " << n << std::endl;
    
    return 0;// 主线程return,表示进程结束! 
}

在任何一个线程调用 exec*()【进程的程序替换】,是不可以的。当我们在进行进程的程序替换的时候,exec* () 是会把整个进程全部给替换掉的【代码和数据全部替换】,代码和数据有可能其他线程也在用,有可能会导致其他线程全部崩掉。

若非要进行程序替换,就可以在线程里面进行 fork() 创建子进程。任何一个线程,创建子进程 fork() 会把当前进程的地址空间、页表、代码和数据 全部拷贝一份,只不过 默认形成的新进程内部只有一个PCB【子进程的PCB】,线程内可以创建进程,再让子进程去调用exec*()。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>

void *start(void *args)
{
    pid_t id = fork();
    if(id == 0)
    {
        //...
    } 
}

int main()
{
    pthread_t tid;
    // 创建进程
    pthread_create(&tid, nullptr, start, (void *)"thread-1");
    // pthread_detach(tid); // 把目标线程进行分离,pthread_join() 也是失败的,进程不会阻塞,直接return退出

    sleep(5);

    // 进程等待
    void *ret = nullptr;
    int n = pthread_join(tid, &ret); // PTHREAD_CANCELED;
    std::cout << "new tjread exit code: " << (long long int)ret << ", n: " << n << std::endl;
    
    return 0;// 主线程return,表示进程结束! 
}

在进程内可以创建 线程,线程内也可以创建进程。


如若对你有帮助,记得关注、收藏、点赞哦~ 您的支持是我最大的动力🌹🌹🌹🌹!!!

若有误,望各位,在评论区留言或者私信我 指点迷津!!!谢谢 ヾ(≧▽≦*)o \( •̀ ω •́ )/

相关推荐
同学小张19 小时前
【端侧AI 与 C++】1. llama.cpp源码编译与本地运行
开发语言·c++·aigc·llama·agi·ai-native
踢球的打工仔20 小时前
PHP面向对象(7)
android·开发语言·php
t1987512821 小时前
在Ubuntu 22.04系统上安装libimobiledevice
linux·运维·ubuntu
skywalk81631 天前
linux安装Code Server 以便Comate IDE和CodeBuddy等都可以远程连上来
linux·运维·服务器·vscode·comate
汤姆yu1 天前
基于python的外卖配送及数据分析系统
开发语言·python·外卖分析
Yue丶越1 天前
【C语言】字符函数和字符串函数
c语言·开发语言·算法
晚风吹人醒.1 天前
缓存中间件Redis安装及功能演示、企业案例
linux·数据库·redis·ubuntu·缓存·中间件
翔云 OCR API1 天前
人脸识别API开发者对接代码示例
开发语言·人工智能·python·计算机视觉·ocr
V***u4531 天前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
Hard but lovely1 天前
linux: pthread库的使用和理解
linux