【Linux】多线程

目录

1.储备知识

1.1.进程的资源划分

1.1.1.先导概念

 1.1.2.虚拟地址转化物理地址

2.线程概念

2.1.Linux线程概念

2.1.1.什么是线程

2.1.2.Linux中多线程的结构

 2.2.多线程的优点

2.3.多线程的缺点

2.4.线程异常

2.5.线程用途

2.6.Linux进程VS线程

3.Linux线程控制

3.1.POSIX线程库

3.1.1.基本概念

3.1.2.语言级别的线程库(语言版)

3.2.创建线程

3.2.1.pthread_create函数原型。

3.2.2.查看线程id

3.2.3.多线程中的资源共享

3.2.4.一次创建多个线程的常见问题

3.3.线程终止

1.return终止

2.pthread_exit终止 

3.pthread_cancel线程取消

3.4.线程等待

3.4.1.pthread_join函数(阻塞等待)

3.5.分离线程(不等待线程)

3.5.1.pthread_self函数(获取自己id)

3.5.2.pthread_detach函数(设置分离)

3.6.什么是线程ID

3.7.线程的局部存储

3.7.模拟c++语法封装一下线程的接口




1.储备知识

1.1.进程的资源划分

1.1.1.先导概念

 1.1.2.虚拟地址转化物理地址

虚拟到物理图解

当然虚拟地址到物理地址的转化并不只是通过页表就可以完成的,还需要硬件MMU的支持。



2.线程概念

2.1.Linux线程概念

2.1.1.什么是线程

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

2.1.2.Linux中多线程的结构

图解:

解释:

OS会给我们听到的OS不会给我们提供创建线程的接口,只会给我提供创建轻量级进程的接口,这个接口是:clone

 其实我们用的fork底层也是用的clone,这个clone允许创建一个进程,也允许创建一个轻量级进程,说白了就是创建进程的时候要不要共享地址空间。此外我们以前学到了创建一个新的进程fork,还有一个vfork,差别在于使用vfork创建的新进程和父进程是共享地址空间的,这不就是轻量级进程的概念吗?感兴趣的可以尝试使用一下。

 2.2.多线程的优点

  1. 创建一个新线程的代价要比创建一个新进程小得多。
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。

                1.进程:切换页表,虚拟地址空间,PCB,CPU寄存器数据(进程上下文)。

                2.线程:PCB,CPU寄存器数据。

                3.CPU高速缓存命中率高,减少ca'che更新次数。

  1. 线程占用的资源要比进程少很多。
  2. 能充分利用多处理器的可并行数量。
  3. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
  4. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
  5. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

2.3.多线程的缺点

  • 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  • 健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  • 缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高

编写与调试一个多线程程序比单线程程序困难得多

2.4.线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

2.5.线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率。
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)。

2.6.Linux进程VS线程

进程是:资源分配的基本单位
线程是:调度的基本单位

线程共享进程数据,但也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器数据(独立的上下文)
  • 栈(独立的栈结构)
  • errno
  • 信号屏蔽字
  • 调度优先级
  • 独立的PCB属性

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

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

进程和线程的关系如下图:


 



3.Linux线程控制

3.1.POSIX线程库

3.1.1.基本概念

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的。
  • 要使用这些函数库,要通过引入头文<pthread.h>。
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

 可以看到可执行程序依赖了pthread库

3.1.2.语言级别的线程库(语言版)

在很多语言级别也存在多线程,一般是对原生线程库进行封装。例如c++中也存在多线程。

#include<iostream>
#include<unistd.h>
#include<thread>//c++的头文件

void thread_run()
{
    while(true)
    {
        std::cout<<"我是新线程......"<<std::endl;
        sleep(1);
    }
}
int main()
{
    std::thread t1(thread_run);
    while(true)
    {
        std::cout<<"我是主线程......"<<std::endl;
        sleep(1);
    }
    t1.join();
    return 0;
}

这段代码全部是c++的语法,但是我们编译的时候不加 -lpthread 库,就会出问题。

 任何语言要在Linux中想要实现多线程,必定使用的是原生线程库(pthread库)。

c++11中的多线程库,本质是对pthread库的封装。

其实就是和open和fopen一样的道理,对底层的东西进行了封装。

使用原生线程库的效率会比较高,但是不可跨平台。

使用语言提供的多线程库,效率相对低一点,但是可跨平台,在Linux和windows都可以跑。

具体使用什么依据情况决定。

3.2.创建线程

3.2.1.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变量的开销更小。

代码案例:

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11 -lpthread 
.PHONY:clean
clean:
	rm -f mythread
//这个函数会给新的线程去调用
void* mythread_coutine(void * agv)
{
    while(true)
    {
        cout<<"新线程正在执行!!"<<endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid ;
    int ret = pthread_create(&tid, nullptr, mythread_coutine, nullptr);
    assert(0 == ret);
    (void)ret;

    while(true)
    {
        cout<<"主线程,正在执行!!"<<endl;
        sleep(1);
    }
    return 0;
}

运行结果:

3.2.2.查看线程id

ps -aL :查看全部轻量级进程

所以OS在进程调度的时候,使用的是LWP线程id来进行调度的。

以前我们学的进程是一个进程里面只有一个执行流,所以进程id(PID)和主线程id(TID)相同。

3.2.3.多线程中的资源共享

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

多个线程的pcb指向同一个虚拟地址空间,再通过同一页表映射到物理内存。所以线程之间的通讯会比进程之间通讯简单的多。成本小的多。

但是每一个线程也有自己私有的资源。

1.私有的pcb属性; 2.私有的上下文; 3.每个线程都有自己独立的栈结构。

3.2.4.一次创建多个线程的常见问题

错误案例:

void* mythread_coutine(void * agv)
{
    //string str = (char*)agv;
    string str = static_cast<const char*>(agv);//安全的进行强制类型转换。

    while(true)
    {
        cout<<str<<endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    vector<pthread_t> tids;
    for(int i =0 ;i<NUM;i++)
    {
        char buf[64];
        snprintf(buf, sizeof(buf),"这是新线程,线程编号是:%d", i);
        pthread_t tid;
        int ret = pthread_create(&tid,nullptr, mythread_coutine, (void*)buf);
        tids.push_back(tid);
    }

    while(true)
    {
        cout<<"主线程,正在执行!!"<<endl;
        sleep(1);
    }

    return 0;
}

运行结果:这样写法就会出现问题,我们看到的结果并不是我们想要的结果。编号不是1,2,3,4---开始的,为什么呢?因为我们传给新线程的参数是缓冲区的起始地址,这个缓冲区在主线程的栈空间里面,会可能会随着主线程的执行不断的被覆盖和重写。新线程和主线程谁先执行是不知道的,所以数据的真实性就消失了。

解决办法:

struct ThreadData{
    pthread_t tid;
    char namebuf[64];
};


void* mythread_coutine(void * agv)
{
    //string str = (char*)agv;
    string str = static_cast<ThreadData*>(agv)->namebuf;
    //安全的进行强制类型转换。

    while(true)
    {
        cout<<str<<endl;
        sleep(2);
    }
    return nullptr;
}

int main()
{
    vector<pthread_t> tids;
    for(int i =0 ;i<NUM;i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuf, sizeof(td->namebuf),"这是新线程,线程编号是:%d", i);
        int ret = pthread_create(&(td->tid),nullptr, mythread_coutine, (void*)td);
        tids.push_back(td->tid);
    }

    while(true)
    {
        cout<<"主线程,正在执行!!"<<endl;
        sleep(1);
    }
    return 0;
}

这样就好了,虽然不是按照0,1,2---顺序执行的(这个和调度器有关),但是编号从0 -到9的线程是可以打印的,不会出现刚刚那种传入参数数据不安全的情况。 这是new了10个空间,上面的情况一个空间被用了10次的区别。

上面的mythread_coutine函数虽然看着是所有的线程调用同一个函数,但是不要担心每一个线程在调用mythread_coutine函数的时候,会分配不同的栈空间,是互不干扰的。此函数出了打印数据cout哪一行,其他是可重入的。

注意:这里为什么?很多的进程都在执行这个函数,却没有发送二义性的问题。这就因为每一个线程都有自己独立的栈结构,这里有人又会说了,虚拟内存中只有一个栈区,但是每个线程为何会有自己的栈结构。这个问题后面会讲到,大家先记住,每个线程都有自己独立的栈结构即可。

3.3.线程终止

线程终止主要有三种方法。线程也有返回值,再线程等待部分讲解。 

1.return终止

这个没有什么好讲解的,注意返回值是一个void* 类型的变量。

但是主线程使用return功能类似于exit进程会直接退出。

2.pthread_exit终止 

 举例:

struct ThreadData{
    pthread_t tid;
    char namebuf[64];
};


void* mythread_coutine(void * agv)
{
    sleep(1);
    //string str = (char*)agv;
    string str = static_cast<ThreadData*>(agv)->namebuf;
    //安全的进行强制类型转换。
    int cnt =10;
    while(cnt--)
    {
        cout<<str<<endl;
        sleep(1);

        // char* p = nullptr;
        // *p = '0';
        // //如果一个线程出问题也会影响其他线程,这就是多线程的健壮性或者鲁棒性较差。
        // //信号是整体发送给进程的,多线程只有一个进程pid。全部会受到信号退出。
    }
    delete agv;
    return nullptr;
    //线程终止可以直接以return的方式进行终止。
    //线程函数走到return的时候就算是终止了。
    
    //exit(1);
    //exit不能用来终止线程,他是用来终止进程的。
    //在任何一个执行流里面调用exit函数都会让整个进程全部终止。

    pthread_exit(nullptr);
    //这pthread库里面的调用接口。和return 几乎相同。
}

int main()
{
    vector<ThreadData*> ptds;
    for(int i =0 ;i<NUM;i++)
    {
        ThreadData* td = new ThreadData();
        //每一次开辟的空间地址是不同的。
        snprintf(td->namebuf, sizeof(td->namebuf),"这是新线程,线程编号是:%d", i);
        int ret = pthread_create(&(td->tid),nullptr, mythread_coutine, (void*)td);
        ptds.push_back(td);
        //sleep(1);
    }

    while(true)
    {
        cout<<"主线程,正在执行!!"<<endl;
        sleep(1);
    }
    return 0;
}

这里我们发现线程退出的时候都带有参数,假设我们先拿到对应退出的参数要怎么做呢?

这就要学习线程等待了。线程等待就是为了拿到线程退出时候对应返回的参数。

3.pthread_cancel线程取消

线程是可以被主线程取消的,线程要被取消,前提是这个线程已经被运行起来了。取消也是线程退出的一种。

举例:

struct ThreadData{
    pthread_t tid;
    char namebuf[64];
};

void* mythread_coutine(void * agv)
{
    sleep(1);
    string str = static_cast<ThreadData*>(agv)->namebuf;
    int cnt =5;
    while(cnt--)
    {
        cout<<"这是新线程,"<<str<<endl;
        sleep(1);
    }
    return (void*)100;
    //正常退出的时候会返回:100
    //但是线程被取消的时候,不是100。
}

int main()
{
    vector<ThreadData*> ptds;
    for(int i =0 ;i<NUM;i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuf, sizeof(td->namebuf),"编号是:%d", i);
        int ret = pthread_create(&(td->tid),nullptr, mythread_coutine, (void*)td);
        ptds.push_back(td);
    }

    //创建的新线程会跑6秒后退出
    //我们在3秒的时候给所有线程发送取消信号。
    sleep(3);
    //取消一半的线程。
    for(int i =0 ; i< ptds.size()/2; i++)
    {
        pthread_cancel(ptds[i]->tid);
        cout<<"pthread_cancel : "<<ptds[i]->namebuf<<endl;
    }
    for(auto e : ptds)
    {
        void* n = nullptr;
        int ret = pthread_join(e->tid, &n);//阻塞式等待。
        assert( 0 == ret); 
        cout<<"join: "<<e->namebuf<<" success" << "取出返回信息:"<<(long long)n<<endl;
        delete e;
        (void)ret;
    }
    cout<<"main thread join succsess"<<endl;
    return 0;
}

结果:

 这就是线程取消,如果线程被取消了,会直接进入被等待状态,线程的返回值就是 -1。这个-1是: PTHREAD_CANCELED的一个宏。值就是-1.

 一般都是用主线程去取消创建的新线程。

3.4.线程等待

线程也是要被等待的,如果不等待,也会造成类似僵尸进程的问题,也会发送内存泄漏。

线程也是必须被等待的:
1.获取收新线程的退出信息。(有时候不关心退出信息,但是还是要等待,释放资源)

2.回收新线程对应的PCB等内核资源 ,防止内存泄漏。(暂时无法查看)

3.4.1.pthread_join函数(阻塞等待)

struct ThreadData{
    pthread_t tid;
    char namebuf[64];
};


void* mythread_coutine(void * agv)
{
    sleep(1);
    //string str = (char*)agv;
    string str = static_cast<ThreadData*>(agv)->namebuf;
    //安全的进行强制类型转换。
    int cnt =10;
    while(cnt--)
    {
        cout<<"这是新线程,"<<str<<endl;
        sleep(1);

        // char* p = nullptr;
        // *p = '0';
        // //如果一个线程出问题也会影响其他线程,这就是多线程的健壮性或者鲁棒性较差。
        // //信号是整体发送给进程的,多线程只有一个进程pid。全部会受到信号退出。
    }
   //delete agv;
   //一般在等待线程之后再释放。
    return nullptr;
    //线程终止可以直接以return的方式进行终止。
    //线程函数走到return的时候就算是终止了。
    
    //exit(1);
    //exit不能用来终止线程,他是用来终止进程的。
    //在任何一个执行流里面调用exit函数都会让整个进程全部终止。
 
    pthread_exit(nullptr);
    //这pthread库里面的调用接口。和return 几乎相同。
}

int main()
{
    vector<ThreadData*> ptds;
    for(int i =0 ;i<NUM;i++)
    {
        ThreadData* td = new ThreadData();
        //每一次开辟的空间地址是不同的。
        snprintf(td->namebuf, sizeof(td->namebuf),"编号是:%d", i);
        int ret = pthread_create(&(td->tid),nullptr, mythread_coutine, (void*)td);
        ptds.push_back(td);
        //sleep(1);
    }


    for(auto e : ptds)
    {
        int ret = pthread_join(e->tid, nullptr);//阻塞式等待。
        assert( 0 == ret);
        cout<<"join: "<<e->namebuf<<"success"<<endl;
        delete e;
        (void)ret;
    }

    cout<<"main thread join succsess"<<endl;


    // while(true)
    // {
    //     cout<<"主线程,正在执行!!"<<endl;
    //     sleep(1);
    // }
    return 0;
}

但是我们发现线程退出的时候 无论式 return还是pthread_exit 都会有一个返回值(void*类型的返回值)。

 举例:

struct ThreadData{
    pthread_t tid;
    char namebuf[64];
};
void* mythread_coutine(void * agv)
{
    sleep(1);
    string str = static_cast<ThreadData*>(agv)->namebuf;
    int cnt =5;
    while(cnt--)
    {
        cout<<"这是新线程,"<<str<<endl;
        sleep(1);
    }
    //return (void*)((char*)agv+sizeof(pthread_t));
    //return (void*)(((ThreadData*)agv)->namebuf);
    //返回对应的线程name。
    //pthread_exit((void*)(((ThreadData*)agv)->namebuf));

    //把数字当地址使用,然后再强转拿到数字。
    //return (void*)22L;
    //return (void*)(long long)22;
    long long * ret = new long long(22);
    return (void*)ret;
}

int main()
{
    vector<ThreadData*> ptds;
    for(int i =0 ;i<NUM;i++)
    {
        ThreadData* td = new ThreadData();
        //每一次开辟的空间地址是不同的。
        snprintf(td->namebuf, sizeof(td->namebuf),"编号是:%d", i);
        int ret = pthread_create(&(td->tid),nullptr, mythread_coutine, (void*)td);
        ptds.push_back(td);
        //sleep(1);
    }

    for(auto e : ptds)
    {
        // char* buf;
        // int ret = pthread_join(e->tid, (void**)(&buf));//阻塞式等待。
        //ThreadData* td = new ThreadData();
        //int ret = pthread_join(e->tid, (void**) (&td));//阻塞式等待。

        // void* n = nullptr;
        // int ret = pthread_join(e->tid, &n);//阻塞式等待。
       
        void* n = nullptr;
        int ret = pthread_join(e->tid, &n);//阻塞式等待。

        assert( 0 == ret); 
        //cout<<"join: "<<e->namebuf<<" success" << "取出返回信息:"<<buf<<endl;
        //cout<<"join: "<<e->namebuf<<" success" << "取出返回信息:"<<(char*)td->namebuf<<endl;
        cout<<"join: "<<e->namebuf<<" success" << "取出返回信息:"<<*(long long*)n<<endl;
        delete (long long *)n;
        delete e;
        (void)ret;
    }

    cout<<"main thread join succsess"<<endl;
    return 0;
}

我们可以看到,返回的形式,有很多的方式,但是注意返回的地址对应的空间应该出了函数作用域还在没有被销毁。当然也可也通过强制类型转化,将数据存入void* 的指针变量里面。然后再强转拿到数据,但是数据只能是8字节或者4字节大小(和环境有关,指针的字节大小)。

总之:返回的数据是一个指针的大小,类型是void*,可以是真的指针,可以是通过数据强转然后存入void*中。

如果返回的数据不只有一个,可以写一个结构,然后再堆区开辟空间,返回堆区空间的起始地址。就可以返回多个数据。

还有个问题,为什么线程退出的时候我们收不到退出信号?进程退出的时候通过等待可以受到对应的退出信号,那么线程的退出信号呢?这是因为如果因为信号线程推出了,那么进程也就退出了,那么还关心信号干啥,进程都退出了还关心信号有什么用,所以pthread_join默认认为线程会调用成功。异常信号是进程该考虑的问题。不是线程该考虑的问题。

3.5.分离线程(不等待线程)

进程存在阻塞等待,非阻塞等待,不等待(设置信号)。线程只存在阻塞等待和不等待。

3.5.1.pthread_self函数(获取自己id)

void * fun(void * s)
{
    string str = static_cast<const char*>(s);
    //static_cast 安全的类型转化,不能转化的就报错了。
    while(true)
    {
        char buf[128];
        snprintf(buf, 128, "0x%x",pthread_self());
        //pthread_self函数获取自己线程的线程tid。
        cout<<str<<", runing......"<<"TID: "<< buf<<endl;
        sleep(1);
    }
}
int main()
{

    pthread_t tid;
    pthread_create(&tid, nullptr,fun, (void*)"thread 1");
    char buf[128];
    snprintf(buf, 128, "0x%x",tid);
    while(true)
    {
        cout<<"main, runing......"<<"TID: "<<buf<<endl;
        //这里打印的值和新线程内部的值肯定是一样的。
        sleep(1);
    }
    pthread_join(tid, nullptr);
    return 0; 
}

结果:

 说明pthread_self函数是可以获取自己的线程id的。

3.5.2.pthread_detach函数(设置分离)

这个函数可以在主线程内使用(主线程分离新线程),也可以在新线程使用(新线程自己分离自己)。传入线程id即可。

void * fun(void * s)
{
    string str = static_cast<const char*>(s);
    //static_cast 安全的类型转化,不能转化的就报错了。
    //pthread_detach(pthread_self());
    //自己把自己设置为分离状态。
    int cnt = 5;
    while(cnt--)
    {
        cout<<str<<"runing....."<<endl;
        sleep(1);
    }

    return nullptr;
}
int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr,fun, (void*)"thread 1");
    assert(0 == n);

    pthread_detach(tid);
    //主线程把新线程设置为分离状态。

    // int ret = pthread_join(tid, nullptr);
    // cout<<"ret:"<<ret<<":"<<strerror(ret)<<endl;
    //线程创建的时候默认是joinable状态的,如果设置分离状态,就不能进行pthread_join等待了。
    //如果设置了分离状态还去用join等待它,就会报错。
    //ret:22:Invalid argument

    while(true)
    {
        cout<<"main, runing....."<<endl;
        sleep(2);
    }
    return 0; 
}

 这样就不会出现内存泄漏的问题了。

3.6.什么是线程ID

实际上线程ID就是地址,在OS内核中本没有线程的概念,在原生线程库中才有线程的概念,所以线程ID就是地址,指向“TCB” 的地址。

举例:

void* fun(void* agv)
{
    char buf[64];
    snprintf(buf, sizeof(buf), "0x%x",pthread_self());//把tid转为十六进制。
    while(true)
    {
        cout<<"new thread runing...."<<"new thread tid:"<<buf<<endl;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid ;
    pthread_create(&tid, nullptr, fun, nullptr);
    char buf[64];
    snprintf(buf, sizeof(buf), "0x%x",tid);//把tid转为十六进制。
    while(true)
    {
        cout<<"main thread runing..."<<"new thread tid:"<<buf<<endl;
        sleep(1);
    }
    return 0;
}

结果:

3.7.线程的局部存储

举例:

int number = 100;

void* fun(void* agv)
{
    sleep(1);
    while(1)
    {
        cout<<"new thread runing..... :    "<<"number: "<<number <<" &number: "<<&number<<endl;
        number++;
        sleep(5);
    }
    return nullptr;
}



int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, fun, nullptr);
    while(true)
    {
        cout<<"main thread runing.... :    "<<"number: "<<number <<" &number: "<<&number<<endl;
        sleep(2);
    }

    return 0;
}

 可以看到这个变量属于两个进程共享,在一个线程里面修改在另一个线程内部是可以看到的。

线程局部存储就是为了让他们互不干扰。在定义变量的时候加上"__thread".

// 添加 __thread  可以将一个内置类型设置为线程的局部存储。

//虽然还是全局变量,但是在编译的时候给每一个线程都来一份,各玩各的了。

举例:


// 添加 __thread  可以将一个内置类型设置为线程的局部存储。
//虽然还是全局变量,但是在编译的时候给每一个线程都来一份,各玩各的了。
__thread int number = 100;

void* fun(void* agv)
{
    sleep(1);
    while(1)
    {
        cout<<"new thread runing..... :    "<<"number: "<<number <<" &number: "<<&number<<endl;
        number++;
        sleep(5);
    }
    return nullptr;
}



int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, fun, nullptr);
    while(true)
    {
        cout<<"main thread runing.... :    "<<"number: "<<number <<" &number: "<<&number<<endl;
        sleep(2);
    }

    return 0;
}

 可以看到原本在已初始化数据段的数据,被放到了对应线程的局部存存储。在共享区中。地址增加了。局部存储可以设置1线程的私有属性。介于全局变量和局部变量之间的变量。

3.7.模拟c++语法封装一下线程的接口

以面向对象的方式管理线程。

#pragma once
//hpp是开源
#include<iostream>
#include<string>
#include<cassert>
#include<functional>
#include<pthread.h>

//声明
class Thread;

struct Context
{
    Thread* _this;
    void* _argc;
    Context():_this(nullptr),_argc(nullptr)
    { }
    ~Context()
    {
    }
};

//template<typename T>

class Thread
{
public:
    //using func_t = std::function<void*(void*)>;
    //using是一个关键字,用于引入命名空间、类型别名或特定成员函数等,以便在当前作用域中可以直接使用这些名称。
    //使用using声明可以简化代码,减少冗余的限定符和命名空间前缀,提高代码的可读性和可维护性。例如:
    // using std::cout; // 引入std命名空间中的cout标识符
    // using namespace std; // 引入整个std命名空间
    // using mynamespace::myFunction; // 引入mynamespace命名空间中的myFunction函数
    typedef std::function<void*(void*)> func_t;
    //typedef void*(*func_t)(void*);

    const int numsize = 1024;
public: 

//因为成员函数会默认隐藏一个参数,所以需要函数应该设置为静态成员函数。
static void* start_routine(void* args)
{
    //return _func(_args);
    //静态成员函数内部不能访问普通成员。
    //主要原因是因为没有this指针

    //怎么办,把他作为参数传进来就行啦。

    Context* ct = static_cast<Context*>(args);
    void* ret =ct->_this->run();
    delete ct;
    return ret;
    //return ct->_this->_func(ct->_this->_argc);
    //如果把_func和_argc公有,也可以直接调用。
}

//执行函数必须给出,但是函数的参数可以默认为nullptr,不传入参数。
    Thread(func_t func, int number, void* args = nullptr):_func(func), _argc(args)
    {
        //给线程起一个名字。
        char buf[numsize];
        snprintf(buf, sizeof buf, "thread-%d", number);
        _name = buf;

        // //或者 使用 to_string()
        // _name = "thread-";
        // _name += std::to_string(number);

        //创建一个线程。
        Context* ct = new Context();
        ct->_argc = _argc;
        ct->_this = this;
        int ret =pthread_create(&_tid, nullptr, start_routine , ct); 
        //int ret =pthread_create(&_tid, nullptr, _func, _args);
        // pthread_create是一个语言接口,提供的都是c语言的参数类型,但是_func是c++的类对象。所以调用出问题了。
        //typedef 设置func类型的时候使用 c++的 语法,但是pthread_create使用的c式接口。
        //这就是c/c++混编的时候会出现的问题。
        //可以使用 函数指针去typedef,
        //typedef void*(*func_t)(void*);
        assert(0 == ret);
        (void)ret;
    }

    void join()
    {

        int ret = pthread_join(_tid, nullptr);
        assert(0 == ret);
        (void)ret;
    }
    std::string& getname()
    {
        return _name;
    }

private:
    void* run()
    {
        return _func(_argc);
    }
    std::string _name;
    pthread_t _tid;
    func_t _func;
    void* _argc; 
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小峰同学&&&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>