前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux——多线程

Linux——多线程

作者头像
有礼貌的灰绅士
发布2023-06-14 11:39:16
9000
发布2023-06-14 11:39:16
举报

Linux多线程

多线程

进程内进行资源划分

之前说过页表有用户级页表和内核级页表,现在再来扩展一下。

在这里插入图片描述
在这里插入图片描述

这里也能解释为什么对于常量字符串类型为什么不能修改了,因为要修改的时候会从虚拟地址转化成物理地址,然后检查权限是否可以修改等等。

如何看待地址空间和页表呢? 1.比如进程看到的堆区,栈区等等各种的资源窗口。 2.页表决定进程真正拥有资源的情况。(因为虚拟地址空间根本无法决定实际情况) 3.合理的对地址空间+页表进行资源划分,我们就可以对一个进程所有资源进行分类。(比如虚拟地址空间中的堆区,栈区,内核区,通过页表映射到不同的物理地址内存)

从虚拟地址到物理地址是怎么样通过页表访问的呢?

在这里插入图片描述
在这里插入图片描述

如果是32位地址的OS,就有232个地址,那么页表是不是就要有232个条目呢?一个条目算上物理地址,是否命中,读写权限等等就需要很多的内存,总共算下来需要的内存是非常庞大的,这样显然是不可能的。 其实物理内存早就被划分好了区域,每一块叫做数据页。(也叫做页框)

在这里插入图片描述
在这里插入图片描述

每一个页框也被管理:

代码语言:javascript
复制
struct Page
{
	//内存属性——4KB
}

然后通过struct Page类型的数组来管理。 并且磁盘中是数据也是被划分成4KB大小一块。 然后再说说虚拟地址空间的地址:32个比特位都有相对应的含义。

在这里插入图片描述
在这里插入图片描述

首先前十个对应的是页目录记录页表的地址,后面的10个是页表记录物理内存的地址,最后面的12个是物理内存起始地址中的偏移量。

在这里插入图片描述
在这里插入图片描述

页表当中储存的就是页框的起始地址,最后通过偏移量来确定在物理内存中的实际大小,212也正好就是4KB。 也就是说其实在查找的时候OS其实只会创建一个页目录和一个页表,其他暂时不用的页表就先不用,也就是说不需要多少内存。

什么是线程

之前对于进程的概念是内核数据结构+进程对应的代码和数据。

在这里插入图片描述
在这里插入图片描述

之前创建一个子进程室友自己的独立性的,如果今天创建多个进程,和第一个进程指向同一个PCB,看到是同一块虚拟地址空间,然后让每个这种“进程”执行虚拟地址空间中的部分代码,这些“进程”就叫做线程。 在Linux下,创建的线程其实就是PCB而已。 因为通过进行资源划分,单个“进程”的执行粒度一定比之前的进程更细。

在这里插入图片描述
在这里插入图片描述

CPU不会看是不是线程,只会去处理每个线程或者是进程。 OS也需要去管理这些线程。 专门管理线程的叫做TCB。 在windows操作系统就是这么设计的,CPU会先找到某个进程,然后进入这个进程中再去找线程。 (这样的设计是很复杂的,也不好维护) 从被执行的角度来看,进程和线程的区别并不是很大。

这就是为什么Linux中的线程只是复用PCB,用PCB来表示“线程”。

线程其实就是进程的一个执行流:

线程在进程的内部运行,线程在进程的地址空间内运行,拥有该进程的一部分资源。

也就是说:

在这里插入图片描述
在这里插入图片描述

这里整体才算是一个完整的进程。 内核视角:承担分配系统资源的基本实体。(创建进程所需要的各种资源) 如果按照概念来说,相对比之前说的进程只有一个执行流,现在的进程是拥有多个执行流。

在Linux中,什么是线程呢?是CPU调度的基本单位。 在Linux中,一个线程被称为轻量级进程。

总结:

1.Linux内核中没有真正意义上的线程,是用PCB来模拟线程的,是一种完全属于自己的一套线程方案。 2.站在CPU的角度,每一个PCB都可以被叫做轻量级进程 3.Linux线程是CPU调度的基本单位,而进程是承担分配系统资源的基本单位。 4.进程是整体申请资源,线程是向进程申请资源。

Linux线程的优点是什么呢? 比Windows操作系统的线程简单,维护成本低,可靠,高效。

线程的具体作用呢? 就像迅雷的边播放边下载。

Linux无法直接提供创建线程的系统调用,只能提供创建轻量级进程的接口。

进一步理解线程

先来用一份代码来看看线程: pthread_create函数介绍

在这里插入图片描述
在这里插入图片描述

第一个参数是线程id,第二个参数是线程属性(大部分情况设置为nullptr),第三个参数是回调函数,让该线程执行这个函数。第四个参数是第三个参数的回调函数的参数。 成功返回0,失败返回错误码。 并且这个函数是第三方库的内容:pthread。 这是因为Linux没有真正意义上的线程。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <unistd.h>
using namespace std;

void *pthread_routine(void* args)
{
    while(true)
    {
        cout << "我是新线程" << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, pthread_routine, (void *)"111111");//参数记得强制转换成void*
    assert(n == 0);
    (void)n;
    while(true)
    {
        cout << "我是主线程" << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

任何Linux操作系统都必须默认携带这个库,这个库叫做原生线程库。

在这里插入图片描述
在这里插入图片描述

运行的时候发现只有一个进程。

在这里插入图片描述
在这里插入图片描述

终止之后就没有了。

然后用ps -al查看轻量级进程。

在这里插入图片描述
在这里插入图片描述

这里有两个执行流,PID相同,说明属于同一个进程,旁边的LWP不同,这个就是轻量级进程的id。 两个id相同的是主线程,不同的是新线程。 这里也说明CPU进行调度的时候是以LWP为标准特定执行流的。

并且,主线程还可以给新线程发送内容:

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <unistd.h>
using namespace std;

void *pthread_routine(void* args)
{
    const char* p = (const char*)args;
    while(true)
    {
        cout << "我是新线程" << p << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, pthread_routine, (void *)"111111");//参数记得强制转换成void*
    assert(n == 0);
    (void)n;
    while(true)
    {
        cout << "我是主线程,新线程id是:" << tid << endl;//这里顺便打印新线程的id
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

新线程确实可以接收到主线程发送的信息,但是新线程的id却非常怪,这里可以用16进制的方式打印出来看看。

在这里插入图片描述
在这里插入图片描述

最后发现这是一个地址,那么是什么地址呢? 这个后面再说。

这里补充一点,线程一旦创建,几乎所有的资源都是被所有线程共享的。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <unistd.h>
using namespace std;

int i = 0;

void *pthread_routine(void* args)
{
    const char* p = (const char*)args;
    while(true)
    {
        cout << "我是新线程"<<  "i:"<< i++ << "&i:" << &i << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, pthread_routine, (void *)"111111");//参数记得强制转换成void*
    assert(n == 0);
    (void)n;
    while(true)
    {
        char buffer[64];
        snprintf(buffer, sizeof(buffer),"0x%x", tid);
        cout << "我是主线程" << "i:"<< i << "&i:" << &i << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

虽然线程共享了大部分资源,但是线程也一定会有私有属性,都有什么呢?

1.线程只要会被CPU调度,那么PCB属性就是私有的。 2.上下文一定是私有的,不然怎么独立调度呢? 3.拥有独立的栈结构。(用来保存自己的数据) 2和3是证明线程动态运行的证据。

与进程之间切换相比,线程需要操作系统左的工作会少很多,为什么呢? 1.线程不需要切换页表和虚拟地址空间 2.CPU中有一个叫做cache,其实就是高速缓存。这个功能就是保存热点数据(就是保存这条代码附近的代码,因为很有可能会访问到附近的代码) 也就是说如果某个进程先让部分代码放入eache中,然后CPU去这里找,如果未命中才会重新去内存中找。 也就是说如果是进程之间切换,不同的进程数据是不共享的,降低了效率。 但是线程的数据是共享的,eache可以不用切换。

总结:

在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。 一切进程至少都有一个执行线程。 线程在进程内部运行,本质是在进程地址空间内运行。 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化。 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

线程的优缺点

优点:

创建一个新线程的代价要比创建一个新进程小得多。 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。 线程占用的资源要比进程少很多。 能充分利用多处理器的可并行数量。 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。 计算密集型应用(CPU,加密,解密,算法等),为了能在多处理器系统上运行,将计算分解到多个线程中实现。 I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

缺点:

性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。(并不是线程越多越好,要合适,最好要和CPU的核数相同) 健壮性降低 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。 缺乏访问控制 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。 编程难度提高 编写与调试一个多线程程序比单线程程序困难得多。

Linux进程VS线程

进程是资源分配的基本单位 线程是调度的基本单位 线程共享进程数据,但也拥有自己的一部分数据:

线程ID 一组寄存器 栈 errno 信号屏蔽字 调度优先级

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

文件描述符表 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数) 当前工作目录 用户id和组id

在这里插入图片描述
在这里插入图片描述

线程的异常

一个线程出异常,会影响到另外线程。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <unistd.h>
using namespace std;

void *pthread_routine(void* args)
{
    while(true)
    {
        cout << "我是新线程" << endl;
        sleep(1);
        int* p = nullptr;
        *p = 0;
    }
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, pthread_routine, (void *)"111111");//参数记得强制转换成void*
    assert(n == 0);
    (void)n;
    while(true)
    {
        cout << "我是主线程" << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

一旦出异常的时候,会给这个进程发送信号,发送信号也是发送给所有线程,然后就会终止所有线程。

创建线程两个的接口

之前说过,程序员只需要线程,但是Linux又不直接提供创建线程的接口,只提供第三方库(软件层次)创建轻量级进程的接口,下面来介绍这些接口。

创建轻量级进程或者进程的底层接口:(区别就是创建的时候是否共享地址空间)

在这里插入图片描述
在这里插入图片描述

第一个参数是新执行流要执行的代码,第二个参数是栈结构。

在这里插入图片描述
在这里插入图片描述

这个接口是和fork差不多,只不过是共享了地址空间。 但是两个接口不是很常用

线程的控制

线程的创建

pthread_create 功能:创建一个新的线程 原型 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变量的开销更小。

首先来创建一组线程来看看:

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <vector>
#include <unistd.h>
using namespace std;

void *pthread_routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        cout << "new " << name << endl;
        sleep(1);
    }
}

int main()
{
    vector<pthread_t> tids;
#define NUM 10
    for(int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        char buffer[64];
        snprintf(buffer,sizeof(buffer),"%s:%d","thread",i);
        int n = pthread_create(&tid, nullptr, pthread_routine, (void *)buffer);
        sleep(1);
    }

    while(true)
    {
        cout << "main thread" << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

如果将主线程的创建线程过程中的sleep注释掉会发生什么呢?

在这里插入图片描述
在这里插入图片描述

有些编号的线程完全没有出现过,这是为什么呢? 因为创建线程之后哪个线程先运行是不确定的,并且:

在这里插入图片描述
在这里插入图片描述

这个函数的最后一个参数传过去的是缓冲区的起始地址。 有时候某些进程先运行:

在这里插入图片描述
在这里插入图片描述

那么这种情况如何避免呢?

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <vector>
#include <unistd.h>
using namespace std;

struct ThreadData
{
    pthread_t tid;
    char buffer[64];
};
void *pthread_routine(void* args)//当前函数是被重入状态,如果忽略打印操作就是可重入函数
{
    ThreadData* p = static_cast<ThreadData*>(args);
    while(true)
    {
        cout << "new " << p->buffer << endl;
        sleep(1);
    }
    delete p;
}
int main()
{
#define NUM 10
    for(int i = 0; i < NUM; i++)
    {
        ThreadData *p = new ThreadData;//这里每个创建的起始地址都不相同
        char buffer[64];
        snprintf(p->buffer,sizeof(p->buffer),"%s:%d","thread",i);
        pthread_create(&p->tid, nullptr, pthread_routine, p);
    }
    while(true)
    {
        cout << "main thread" << endl;
        sleep(1);
    }
    return 0;
}
在这里插入图片描述
在这里插入图片描述

那么pthread_routine为什么能可重入呢? 因为每一个线程都有自己独立的栈结构。

线程的终止

之前讲过一个exit的函数,这个函数是让进程终止,而不是线程,如果当前进程中任何一个执行流调用了exit函数,那么当前进程都会退出。 那么如何终止一个线程呢?函数中的return可以,还有一个函数可以:

在这里插入图片描述
在这里插入图片描述

参数默认设置为nullptr就可以了。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;

struct ThreadData
{
    pthread_t tid;
    char buffer[64];
};
void *pthread_routine(void* args)
{
    ThreadData* p = static_cast<ThreadData*>(args);
    while(true)
    {
        cout << "new " << p->buffer << endl;
        sleep(1);
        pthread_exit(nullptr);//线程终止
    }
    delete p;
}

int main()
{
#define NUM 10
    for(int i = 0; i < NUM; i++)
    {
        ThreadData *p = new ThreadData;
        char buffer[64];
        snprintf(p->buffer,sizeof(p->buffer),"%s:%d","thread",i);
        pthread_create(&p->tid, nullptr, pthread_routine, p);
        //sleep(1);
    }

    while(true)
    {
        cout << "main thread" << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

线程的等待

线程也是要被等待的,如果不等待会造成类似于僵尸进程的问题——内存泄漏。 线程被等待的原因: 1.获取新线程退出的信息。 2.回收新线程对应PCB内核资源等,防止内存泄漏。(这里暂时无法查看)

这是等待线程的函数:

在这里插入图片描述
在这里插入图片描述

第一个参数是线程的id,第二个参数暂时设置为nullptr。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;

struct ThreadData
{
    pthread_t tid;
    char buffer[64];
};
void *pthread_routine(void* args)
{
    ThreadData* p = static_cast<ThreadData*>(args);
    while(true)
    {
        cout << "new " << p->buffer << endl;
        sleep(1);
        pthread_exit(nullptr);
    }
}

int main()
{
    vector<ThreadData*> arr;
#define NUM 10
    for(int i = 0; i < NUM; i++)
    {
        ThreadData *p = new ThreadData;//这里每个创建的起始地址都不相同
        char buffer[64];
        snprintf(p->buffer,sizeof(p->buffer),"%s:%d","thread",i);
        pthread_create(&p->tid, nullptr, pthread_routine, p);
        arr.push_back(p);
        //sleep(1);
    }
    for(auto& e : arr)
    {
        cout << "等待线程id:" << e->tid << "成功" << endl;
        int n = pthread_join(e->tid, nullptr);//阻塞等待
        assert(n==0);
        delete e;
    }
    while(true)
    {
        cout << "main thread" << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

那么,该函数的第二个参数是什么呢?其实是线程的返回值。(输出型参数)

在这里插入图片描述
在这里插入图片描述

这里返回的是一个void*,输出型参数用一个void**才能传回。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;

struct ThreadData
{
    int num;//线程的编号
    pthread_t tid;
    char buffer[64];
};
void *pthread_routine(void* args)
{
    ThreadData* p = static_cast<ThreadData*>(args);
    while(true)
    {
        cout << "new " << p->buffer << endl;
        sleep(1);
        pthread_exit(nullptr);
    }
    return (void*)p->num;//这里返回的是一个整数,这里强制转换成void*就相当于将四字节的内容写入了八字节的空间里,void* ret = (void*)p->num;
}

int main()
{
    vector<ThreadData*> arr;
#define NUM 10
    for(int i = 0; i < NUM; i++)
    {
        ThreadData *p = new ThreadData;
        p->num = i+1;
        char buffer[64];
        snprintf(p->buffer,sizeof(p->buffer),"%s:%d","thread",i);
        pthread_create(&p->tid, nullptr, pthread_routine, p);
        arr.push_back(p);
        //sleep(1);
    }
    for(auto& e : arr)
    {
        void* ret = nullptr;//注意,这里是void*
        int n = pthread_join(e->tid, &ret);//这里传过去的是ret的地址,等待传回来的返回值是void*的,就等于直接写入的ret当中
        assert(n==0);
        cout << "等待线程:" << e->buffer << "成功"<< "编号:" << (long long)e->num << endl;//因为是64位系统,需要转换成longlong类型 
        delete e;
    }
    while(true)
    {
        cout << "main thread" << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

同理,线程中再堆区开辟的空间地址也可以拿到。

线程取消

注意:线程取消的前提是线程已经在运行了。

在这里插入图片描述
在这里插入图片描述

参数是线程id。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;

struct ThreadData
{
    int num;//线程的编号
    pthread_t tid;
    char buffer[64];
};
void *pthread_routine(void* args)
{
    ThreadData* p = static_cast<ThreadData*>(args);
    while(true)
    {
        cout << "new " << p->buffer << endl;
        sleep(1);
    }
    return (void*)100;//正常退出就返回100
}

int main()
{
    vector<ThreadData*> arr;
#define NUM 10
    for(int i = 0; i < NUM; i++)
    {
        ThreadData *p = new ThreadData;
        p->num = i+1;
        char buffer[64];
        snprintf(p->buffer,sizeof(p->buffer),"%s:%d","thread",i);
        pthread_create(&p->tid, nullptr, pthread_routine, p);
        arr.push_back(p);
        //sleep(1);
    }
    sleep(3);
    for(auto& e : arr)
    {
        pthread_cancel(e->tid);
        cout << "取消的线程id:" << e->tid << "成功" << endl;
    }
    for(auto& e : arr)
    {
        void* ret = nullptr;
        int n = pthread_join(e->tid, &ret);
        assert(n==0);
        cout << "等待线程:" << e->buffer << "成功"<< "编号:" << (long long)ret << endl;//因为是64位系统,需要转换成longlong类型 
        delete e;
    }
    while(true)
    {
        cout << "main thread" << endl;
        sleep(1);
    }

    return 0;
}
在这里插入图片描述
在这里插入图片描述

线程一旦被取消,退出码就是-1。

C++的线程库

C++当中有一个创建多线程的函数: https://legacy.cplusplus.com/reference/thread/thread/?kw=thread

在这里插入图片描述
在这里插入图片描述

但是这里要注意:任何语言在Linux中要实现多线程,必定要使用pthread库。 C++11中的多线程,本质就是对pthread库的封装。

线程的分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。 这种就是线程分离。 这是获得当前调用这个函数id的接口:

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;

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

void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        cout << name << "running" << changid(pthread_self()) << endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, start_routine, (void*)"thread");
    cout << "main thread running... new thread id:" << changid(tid) <<endl;
    pthread_join(tid, nullptr);

    return 0;
}
在这里插入图片描述
在这里插入图片描述

然后来看线程分离的接口:

在这里插入图片描述
在这里插入图片描述

参数是线程id。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;

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

void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);
    pthread_detach(pthread_self());//设置自己为分离状态
    int count = 5;
    while(count)
    {
        cout << name << "running" << changid(pthread_self()) << endl;
        count--;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, start_routine, (void*)"thread");
    string main_tid = changid(pthread_self());
    cout << "main thread running... new thread id:" << changid(tid) << "main thread id:" << main_tid << endl;
    sleep(2);//这里是为了防止新线程还没有进行分离主线程就已经开始等待了
    //如果线程设置了分离,这里就不能等待了
    int n = pthread_join(tid, nullptr);
    cout << "result" << n << ":" << strerror(n) << endl;//这里进行报错之后继续向下运行代码
    return 0;
}
在这里插入图片描述
在这里插入图片描述

如何理解每个线程都有自己独立的栈结构

首先了解一下线程库和轻量级进程的关系:

在这里插入图片描述
在这里插入图片描述

我们用户都是通过pthread库来创建线程的。 在原生线程库当中,我们用这些接口创建的线程别人也可以同时使用。(因为是共享库) 并且也需要对这些线程进行管理:

在这里插入图片描述
在这里插入图片描述

每个结构体对应一个轻量级的进程。 Linux的方案;用户级线程,这些属性在库中,内核提供线程执行流的调度。 Linux用户级线程:Linux内核轻量级进程 == 1:1

那么线程的id究竟是什么呢?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

也就是说一旦线程结束,通过返回值就会传给共享区的TCB中。 这也能说明为什么每个线程都有自己的栈结构了。 主线程使用的栈是在主线程栈,其他线程的栈是在共享区。(其实也就是线程库当中)

那么什么是线程的局部存储呢? 之前创建过一个全局变量,证明两个线程都会共享这个变量。 如果在这个变量前面加上:__thread就可以将一个内置类型设置为线程局部存储。 也就是说给每个线程都来一份这个变量,两个线程在使用这个变量的时候互不影响。 如果以后给线程设置私有属性可以加上这个。

封装线程接口

这里就用Linux的线程接口来实现C++中的多线程部分功能。

代码语言:javascript
复制
#include <iostream>
#include <pthread.h>
#include <cassert>
#include <string>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <memory>
using namespace std;

class Thread;//声明
class Context//上下文,相当于一个大号的结构体
{
public:
    Thread *this_;
    void* args_;
public:
    Context():this_(nullptr),args_(nullptr)
    {}
    ~Context()
    {}
};
class Thread
{
    typedef function<void* (void*)> func_t;
public:
    //这里需要加一个静态,因为不加静态就是类成员函数,还有一个隐藏的this指针,也就说明这等于前面有一个缺省参数
    //所以在类内创建线程,想让对应的线程执行方法需要在方法前面加一个static
    static void* start_routine(void* args)
    {
        //但是静态方法不能调用成员方法或者成员变量,这里可以设置一个上下文
        Context* ctx = static_cast<Context*>(args);
        void* ret = ctx->this_->run(ctx->args_);//这里让自身去调用这个方法
        delete ctx;
        return ret;
    }
    void* run(void* args)
    {
        return _func(args);//调用该函数
    }
    Thread(func_t func,void* args,int num):_func(func),_args(args)
    {
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "thread_%d", num);
        _name = buffer;

        Context* ctx = new Context();
        ctx->this_ = this;
        ctx->args_ = _args;//这里是将自身的部分数据传给ctx
        int n = pthread_create(&_tid, nullptr, start_routine, ctx);//这里要通过调用函数来转化,直接传func是不行的,因为类型是C++的类,不是C语言的类
        assert(n==0);
        (void)n;
    }
    void join()
    {
        int n = pthread_join(_tid,nullptr);
        assert(n==0);
        (void)n;
    }
    ~Thread()
    {}
private:
    string _name;//线程名字
    pthread_t _tid;//线程id
    func_t _func;//线程调用的函数
    void* _args;//传给函数的参数
};
代码语言:javascript
复制
#include "Thread.hpp"

void* thread_run(void* args)
{
    string work_type = static_cast<const char*>(args);
    while(true)
    {
        cout << "我是一个新线程:" << work_type <<endl;
        sleep(1);
    }
}
int main()
{
    unique_ptr<Thread> thread1(new Thread(thread_run, (void*)"hellothread",1));
    unique_ptr<Thread> thread2(new Thread(thread_run, (void*)"countthread",2));
    unique_ptr<Thread> thread3(new Thread(thread_run, (void*)"logthread",3));
    thread1->join();
    thread2->join();
    thread3->join();

    return 0;
}
在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-06-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Linux多线程
  • 多线程
    • 进程内进行资源划分
      • 什么是线程
        • 进一步理解线程
          • 线程的优缺点
            • Linux进程VS线程
              • 线程的异常
              • 创建线程两个的接口
              • 线程的控制
                • 线程的创建
                  • 线程的终止
                    • 线程的等待
                      • 线程取消
                        • C++的线程库
                          • 线程的分离
                            • 如何理解每个线程都有自己独立的栈结构
                            • 封装线程接口
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档