Linux 多线程,线程分离

1.多线程

1.创建多线程

示例代码:

使用for循环去创建一个一个的线程,让它们去循环的打印出自己的pid和线程tid,创建完线程之后不要忘记去释放掉线程资源,避免内存泄漏。

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread"+to_string(i);
}
void*threadRoutine(void*args)
{
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    while(i<10)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<endl;
        sleep(1);
        i++;
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }
}

使用ps-aL来查看监控中的轻量化进程,查看创建情况。

cpp 复制代码
while :; do ps -aL|head -1&&ps -aL|grep my|grep -v grep;sleep 1;done

运行结果:

从左侧代码的运行结果可以看出来这些线程的pid都是一样的,说明都是一个进程创建出来的,右侧可以看出来pid一样但是LWP不一样,说明确实瞬间创建出来了多个线程。

ps:这里也应该明白虽然线程好像是创建在不同的堆上面,好像看样子是每一个线程都拥有了自己的空间,互相不打扰,但是线程之间是没有秘密的,一个线程想要知道其他线程的数据是很简单的,之前找到了tid是动态库存储线程数据的起始地址,只要线程获取到了这个tid也就可以获取到数据了。

2.线程中的变量和全局变量

2.1 线程中创建的变量

在单个线程中,每个线程都是有自己的独立线程栈的,所以在线程执行函数里面定义了一个变量,这个变量会在每一个线程中存在一份。

示例代码:在线程执行函数里面定义一个变量,查看线程中变量的地址,和在对test_i加加两次后,test_i的值。

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread"+to_string(i);
}
void*threadRoutine(void*args)
{
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    int test_i=0;
    while(i<2)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<" "<<&test_i<<": "<<test_i<<endl;
        sleep(1);
        i++;
        test_i++;
        
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }
}

运行结果:

可以看到有10个test_i的地址,说明每一个线程中都存在这一个变量,并且对自己的变量进行操作,没有影响其他的线程。

2.2 全局变量

如果这个变量是全局变量的话,那情况就不一样了,因为全局变量只有一份,线程肯定是看得到这个全局变量的,所以在多线程中对这个变量进行操作,就都是对这个全局变量进行操作了

代码示例:

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
int test_i=0;
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread"+to_string(i);
}
void*threadRoutine(void*args)
{
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    while(i<2)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<" "<<&test_i<<": "<<test_i<<endl;
        sleep(1);
        i++;
        test_i++;
        
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }
}

运行结果:

可以看到所有的线程都是对全局变量进行操作,像这种全局变量会被多个资源访问到,那么这个变量就可以称为是被多个线程共享的'共享资源'。

3._thread关键字(两个

__thread是GCC编译器内置的关键字,正式名称叫:线程局部存储(Thread-Local Storage,TLS)

使用__thread修饰的变量,会在每一个线程中有一个独立的副本。

ps:这个是编译器内置的关键字,所以只能修饰内置类型,不能修饰自定义类型。

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
__thread int test_i=0;
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread"+to_string(i);
}
void*threadRoutine(void*args)
{
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    while(i<2)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<" "<<&test_i<<": "<<test_i<<endl;
        sleep(1);
        i++;
        test_i++;
        
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }
}

运行结果:

可以发现确实在每一个线程中都单独存在了一个变量,并且只对自己的线程中的变量进行操作。

4. 线程访问其他线程的栈

上面我们提到了在线程中是没有秘密的,如果一个线程想要获取到其他线程的数据,是可以做到的,这里我们定义一个全局变量,在主线程中对线程5的i值进行改变,这样子线程5在while循环中只会打印两次,其他线程则会打印3次。

示例代码:

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
int *p=nullptr;
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread-"+to_string(i);
}
void*threadRoutine(void*args)
{
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    if(td->threadname=="thread-5")
    {
            p=&i;
    }
    while(i<3)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<endl;
        sleep(1);
        i++;
        
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    sleep(2);
    *p=2;
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }
}

运行结果:

5. 线程分离

创建一个线程就是创建一个PCB,所以对于线程来说,主线程也是要等待其他线程的,但是如果线程一直不退出,那主线程不是就一直卡住了吗,如果主线程要去做其他的事情不就也做不了了。

如果我们对于线程的退出结果并不关心,就可以让操作系统帮我们管理线程,线程执行完了直接退出,由操作系统来释放该资源。

cpp 复制代码
#include <pthread.h>

int pthread_detach(pthread_t thread);

thread参数:线程的tid。

代码示例1:

主线程在创建线程完成后,分离线程但是不退出,最后我们应该通过监控脚本可以看到3秒后只剩下主线程还在运行,其他线程已经退出(被操作系统释放)。

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread-"+to_string(i);
}
void*threadRoutine(void*args)
{
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    while(i<3)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<endl;
        sleep(1);
        i++;
        
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    for(int i=0;i<tids.size();i++)
    {
        pthread_detach(tids[i]);
    }
    while(true)
    {}
    return 0;
}

运行结果:

示例代码2:线程自己也可以获取到自己的tid,所以也可以在线程内部调用pthread_detach让线程分离。

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread-"+to_string(i);
}
void*threadRoutine(void*args)
{
    pthread_detach(pthread_self());
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    while(i<3)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<endl;
        sleep(1);
        i++;
        
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    // for(int i=0;i<tids.size();i++)
    // {
    //     pthread_detach(tids[i]);
    // }
    while(true)
    {}
    return 0;
}

示例代码3:如果让线程等待了又分离线程会怎么样呢?

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;
#define NUM 8
struct ThreadData
{
    string threadname;
};
string Tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void InitThreadname(ThreadData*td,int i)
{
    td->threadname="thread-"+to_string(i);
}
void*threadRoutine(void*args)
{
    // pthread_detach(pthread_self());
    ThreadData*td=static_cast<ThreadData*>(args);
    int i=0;
    while(i<3)
    {
        cout<<"pid: "<<getpid()<<", tid : "<<Tohex(pthread_self())<<", threadname: "<<td->threadname<<endl;
        sleep(1);
        i++;
        
    }
    delete td;
    return nullptr;
}                                                    
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        //这样子写是不可以的理论上面这个是在主线程创建的,所以还是创建是在主线程的栈上面,for循环结束了也就销毁了
        //ThreadData td;
        ThreadData*td=new ThreadData;//创建在堆上面,每次创建都访问的是不同的堆空间,这样子每一个线程都原来属于自己的堆空间
        InitThreadname(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
    }
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }
    for(int i=0;i<tids.size();i++)
    {
        pthread_detach(tids[i]);
    }
    return 0;
}

运行结果:

会出现段错误的问题,显然分离和等待只能二选一。

ps:线程分离也就是获取到了pthread库中存储的线程描述符tcb的起始地址,tcb中有一个字段表示该线程是否分离,将其改为1表示分离状态。

相关推荐
woho77889933 分钟前
不同网段IP的网络打印机,打印、扫描设置
运维·服务器·网络
耗子会飞40 分钟前
小白学习固定VM虚拟机的centos服务器的IP
运维·服务器·centos
dddddppppp1231 小时前
qemu模拟的一个内核驱动 io口中断
linux
程序员老赵1 小时前
超全 Docker 镜像源配置指南|Windows/Mac/Linux一键搞定,拉镜像再也不卡顿
linux·后端·容器
门豪杰1 小时前
Ubuntu下安装Claude Code
linux·运维·ubuntu·claude·claude code
总要冲动一次1 小时前
离线安装 percona-xtrabackup-24
linux·数据库·mysql·centos
新新学长搞科研2 小时前
第五届电子、集成电路与通信技术国际学术会议(EICCT 2026)
运维·人工智能·自动化·集成测试·信号处理·集成学习·电气自动化
桌面运维家2 小时前
Windows/Linux双启动:BIOS/UEFI多配置桌面创建指南
linux·运维·windows
xlp666hub2 小时前
【Linux驱动实战】:字符设备驱动之内核态与用户态数据交互
linux·面试
無法複制2 小时前
debian安装Postgresql-14.x
运维·postgresql·debian