Linux基础内容(25)—— 线程控制和线程结构

Linux基础内容(24) —— 线程概念_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131294692?spm=1001.2014.3001.5501

目录

1.线程操作

1.线程创建问题

2.线程终止问题

1.exit退出

2.pthread_exit退出

 3.直接退出

3.线程等待问题

信号问题

4.线程取消

5.线程分离

2.理解线程库

重新认识线程库

1.语言层面

2.结构设计

3.pthread_id的含义


1.线程操作

1.线程创建问题

Linux基础内容(25)—— 线程控制和线程结构_第1张图片

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void *start_routine(void *args)
{
    ThreadData *td = static_cast(args); // 安全的强制类型转换
    int cnt = 10;
    while (cnt)
    {
        cout << "new thread success, name: " << td->namebuffer << " cnt: " << cnt << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}

int main()
{
    vector tids;
#define NUM 10
    for (int i = 0; i < NUM; i++)
    {
        ThreadData *td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i);
        pthread_create(&td->tid, nullptr, start_routine, (void *)td);
        tids.push_back(td);
    }
    while (true)
    {
        cout << "new thread success, name: main thread" << endl;
        sleep(1);
    }
    return 0;
}

1.我们创造了10个线程同时进入同一个函数,该函数为可重入状态。start_routine就是可重入函数

2.此时的函数是可重入函数,因为没有出现问题。这些cnt都是临时变量,在各自的线程之中,不会互现影响,也反映了线程有自己的独立栈结构

3.循环式的创造线程不能直接循环,这样可能内部的缓冲区没有被修改,线程就创造了一堆,使得每一个线程的信息都一样

2.线程终止问题

1.exit退出

由于线程的健壮性问题,我们在任意一个执行流中调用ecit函数,都会使得整个进程退出。因为exit针对的对象为进程,我们的信号发送给进程后,自然进程结束,线程也跟着结束。

2.pthread_exit退出

Linux基础内容(25)—— 线程控制和线程结构_第2张图片

 3.直接退出

如果线程执行结束,直接在函数中return。操作系统会自动回收线程

3.线程等待问题

其实线程跟进程一样都需要在结束后等待操作系统回收。如果不等待,线程对应的PCB也没有被释放,那么就会出现类似于进程的僵尸进程的内存漏泄问题。不过可以回收线程释放的信息,只释放线程。

void *start_routine(void *args)
{
    ThreadData *td = static_cast(args); // 安全的强制类型转换
    int cnt = 10;
    while (cnt)
    {
        cout << "new thread success, name: " << td->namebuffer << " cnt: " << cnt << endl;
        sleep(1);
    }
    //delete td;
    return nullptr;
}

for(auto &iter : threads)
{
    int n = pthread_join(iter->tid, nullptr);
    assert(n == 0);
    cout << "join : " << iter->namebuffer << " success " << endl;
    delete iter;
}

//注意为了让已经释放空间的线程能打出其对应的名字,我们需要把start_routine中delete td进行删除
//因为我们不想要早其打印之前就被销毁,那么只能在其打印后进行释放

Linux基础内容(25)—— 线程控制和线程结构_第3张图片

 对指定的thread进行等待,viod**为二级指针是线程的返回的参数。

其实线程join等待回收能将线程exit的数据进行回收

Linux基础内容(25)—— 线程控制和线程结构_第4张图片

class ThreadData
{
public:
    int number;
    pthread_t tid;
    char namebuffer[64];
};

void *start_routine(void *args)
{
    ThreadData *td = static_cast(args); // 安全的强制类型转换
    int cnt = 10;
    while (cnt)
    {
        cout << "new thread success, name: " << td->namebuffer << " cnt: " << cnt << endl;
        sleep(1);
        // int *p = nullptr;
        //  p=NULL;
        //*p=0;
    }
    //delete td;
    return (void*)td->number;
}

    for(auto &iter : threads)
    {
        void* ret=nullptr;
        int n = pthread_join(iter->tid, &ret);
        assert(n == 0);
        cout << "join : " << iter->namebuffer << " success, number: " <<(int_least32_t)ret<< endl;
        delete iter;
    }

 这样就能接受到信息了。当然我们也可以返回一个结构体,这样调回来的就是结构体的信息了。

信号问题

线程出问题,不会收到信号,因为如果线程挂了,进程都退出了。那么也就意味着join等待不会考虑信号的问题。

4.线程取消

线程能被其他线程取消

Linux基础内容(25)—— 线程控制和线程结构_第5张图片

5.线程分离

1.线程的等待功能只有阻塞等待

2.如果线程释放时,我们需要看到线程的返回信息,那么join的函数就可以实现我们需要的

3.如果我们不想要线程的信息,也就意味着join的回收信号是无意义的

4.那么我们不能像进程那样非阻塞等待,是否可以像线程类似通过信号进行自动回收,答案是肯定的,线程分离就能将线程自动被操作系统回收

pthread_self:哪个线程调用,就得到哪个线程的pthread_t

Linux基础内容(25)—— 线程控制和线程结构_第6张图片

std::string changeId(const pthread_t& pthread_id)
{
    char tid[128];
    snprintf(tid,sizeof(tid),"0x%x",pthread_id);
    return tid;
}

void *start_routine(void *args)
{
    std::string threadname = static_cast(args); // 安全的强制类型转换
    while (true)
    {
        std::cout << threadname << " running ...: " << changeId(pthread_self()) << std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, start_routine, (void *)"thread 1");
    std::string main_id=changeId(pthread_self());

    std::cout << "main thread running ...: " << changeId(tid) << std::endl;
    pthread_join(tid, nullptr);
    return 0;
}

两个地址完全一致,说明知道指向的线程地址是多少

pthread_detach:分离一个指定的线程

 Linux基础内容(25)—— 线程控制和线程结构_第7张图片

2.理解线程库

1.重新认识线程库

1.语言层面

mypthread:mypthread.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f mypthread
#include 
#include 
#include 

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;
}

若这样,编译器编不过,系统提示我们没有包含pthread库

1.C++11的多线程在linux的环境中其实是对pthread库的封装,也就意味着,c++创造的线程在linux下依然是轻量级进程

2.不管是什么语言,在linux的环境下都会调用pthread库。

2.结构设计

1.linux没有真正意义上的线程,只有轻量级进程。也就意味着linux提供的调用函数为轻量级进程

2.但是作为用户不会考虑linux的环境和设计,需要的是线程

3.pthread库提供的线程库就是调用linux对轻量级进程的调用函数的封装。那么也就是说其实我们使用的线程其实是库提供给我们的

4.既然库提供给我们对于的结构了,那么我们使用时一定是被管理的。那么库给我们提供的pthread就有其属性,以至于用来记录所有线程的信息

5.pthread_attr_t就是存储线程属性的结构联合体,线程的属性比进程的属性要少。

6.上面的设计其实就是用户及线程,即用户关心的线程属性在库中,内存提供了线程的函数会调用

7.用户级线程与内核轻量级进程一一对应,达到管理的作用

Linux基础内容(25)—— 线程控制和线程结构_第8张图片

3.pthread_id的含义

Linux基础内容(25)—— 线程控制和线程结构_第9张图片

1.在虚拟内存地址中,对应的栈就是主线程的栈

2.线程库其实就是磁盘的文件,它在被调用后会在虚拟内存的共享区中出现

Linux基础内容(25)—— 线程控制和线程结构_第10张图片3.共享区中存储着线程结构体的地址,而多个线程则是通过数组的形式排列存储在虚拟空间

4.共享区指向动态库找到映射的线程,线程中存储有局部存储和独立栈

5.那么我们就能进一步理解所谓的线程库就是封装了linux中的clone轻量级进程

6.增加__thread在内置类型全局变量前,会使得内置类型转换为线程的局部存储中,每一个线程都来一份

__thread int cnt = 0;

你可能感兴趣的:(Linux和操作系统,c++,运维,服务器,linux,centos)