【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 \( •̀ ω •́ )/

相关推荐
技术猿188702783513 分钟前
实现“micro 关键字搜索全覆盖商品”并通过 API 接口提供实时数据(一个方法)
开发语言·网络·python·深度学习·测试工具
放飞自我的Coder10 分钟前
【colab 使用uv创建一个新的python版本运行】
开发语言·python·uv
艾莉丝努力练剑28 分钟前
【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)
c语言·开发语言·数据结构·学习·算法·链表
zyhomepage30 分钟前
科技的成就(六十九)
开发语言·网络·人工智能·科技·内容运营
珊瑚里的鱼36 分钟前
第十三讲 | map和set的使用
开发语言·c++·笔记·visualstudio·visual studio
逑之1 小时前
C++笔记1:命名空间,缺省参数,引用等
开发语言·c++·笔记
songroom1 小时前
【转】Rust: PhantomData,#may_dangle和Drop Check 真真假假
开发语言·后端·rust
黎茗Dawn1 小时前
连接new服务器注意事项
linux·python
RealmElysia1 小时前
java反射
java·开发语言
L_autinue_Star1 小时前
从0到1实现Shell!Linux进程程序替换详解
linux·运维·服务器·c++·chrome