Linux线程实现方案

一、线程的概念

课本上:线程是比进程更加轻量化的一种执行流/线程是在进程内部执行的一种执行流

我们的观点:线程是CPU调度的基本单位/进程是承担系统资源的基本实体

地址空间是进程的“资源”窗口,每个进程访问资源都是通过进程地址空间的,每个进程可以通过进程地址空间+页表实现资源共享或资源独立。

创建的多个线程通过这个资源窗口访问资源。

教材中操作系统是一个概念,没有具体的实体。因为在不同的操作系统中,如Linux,Windows等,它们的实现方案都符合操作系统的概念,但是具体实现形式不同。

创建进程相对线程代价更大,创建线程只需要创建一个PCB,并没有进行过多的资源申请,而是参与资源分配。

线程为什么被称为比进程更加轻量化的一种执行流?

因为线程创建更简单,对应的释放也更简单。进程释放进程地址空间、页表、代码数据等都要释放。线程释放只需要把线程的PCB释放掉就行。

线程是在进程内部执行的一种执行流?

因为线程在进程的地址空间内运行。

一个进程中有多个线程,有许多线程,OS就要把线程管理起来。

如何管理?先描述,再组织。再创建一个TCB线程结构体?填充线程id、线程调度队列、线程上下文、线程状态优先级?这要具体到不同OS的实现。

Linux中没有选择单独再为线程创建线程TCB数据结构,由于线程需要具有的属性,进程PCB中已经包含了,所以Linux选择直接复用进程的代码。这种只创建PCB,让多个PCB指向同一个地址空间,把一个进程的大部分资源分配给每一个执行流的解决方案是Linux中线程的实现方案。

而Windows中则是另外给线程专门设计了一套接口,进程有个优先级,线程也搞个优先级。进程有个调度算法,线程也搞个调度算法等等,最后在系统层面把两个数据结构相关联。

相对Windows的方法,Linux的方法更优,因为Linux中直接复用进程的代码,比较简单,简单则说明更可靠,健壮性很好。维护成本也更低。所以Linux更多被选择作为企业端的后端服务器。

CPU需要区分进程和线程吗?

不需要,就如快递员不需要知道包裹中装的是洗面奶还是面膜。CPU只需要知道它是个执行流就会调度运行它。

蓝框框起来的部分我们称之为进程,内核数据结构(PCB+进程地址空间+页表)+代码和数据,只不过相比以前我们认为的进程多了几个执行流(线程),所以说进程是承担资源分配的实体。

Linux中并不存在真正的线程,只使用进程的数据结构模拟了线程。CPU在调度时看到一个PCB,执行该PCB所对应的方法,该方法只要在进程地址空间上被分配了一部分资源。CPU就执行了进程代码的一部分,访问数据的一部分。所以在CPU上真实跑的执行流比我们认为的传统进程(只有一个PCB)量级要轻一些,所以把这些PCB叫做轻量级进程,即使只有一个PCB,CPU也认为它是一个轻量级进程,因为进程的概念是上面的蓝框部分。轻量级进程就对应课本上线程的概念。所以CPU不会去区分这个PCB是进程中唯一一个执行流还是多个执行流中的一个。

二、创建一个线程

通过pthread_create方法创建线程。参数一是线程id,参数二可以设置线程的属性,一般设为nullptr,参数三是函数指针,即该线程要执行的方法,参数四是函数的参数

#include <iostream>
#include <unistd.h>
#include <pthread.h>


void* ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    while(true)
    {
        std::cout << "i am a thread: " << threadname << std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    //创建一个线程,第三个参数是函数指针(就是让这个线程执行这个函数),第四个参数是函数的参数。
    pthread_create(&tid, nullptr, ThreadRoutine, (void*)"thread 1");

    //主线程
    while(true)
    {
        std::cout << "i am main thread" << std::endl;
        sleep(1);
    }

    return 0;
}

但是当我们编译链接时却有一个报错:未定义的引用,pthread_create。

在我们链接时,需要引入一个pthread库。在后面加上-lpthread就可以链接了

把pthread原生线程库链接上就可以了

 

只有一个进程,实现两个循环输出

我们用ps命令查看线程

ps -aL   //L表示轻量级进程的意思

我们看到了两个testpthread

两个线程的pid是一样的。又有个LWP(Light Weight Process)(也在PCB中),它就是轻量级进程。OS层面上是通过LWP识别这两个轻量级进程的。PID和LWP一样的线程是主线程。

由于线程之间共享地址空间,大部分资源都共享,所以如下程序

#include <iostream>
#include <unistd.h>
#include <pthread.h>


int cnt = 100;

void* ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    while(true)
    {
        std::cout << "i am a thread: " << threadname << " cnt:" << cnt << " &cnt:" << &cnt << std::endl;
        cnt--;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    //创建一个线程,第三个参数是函数指针(就是让这个线程执行这个函数),第四个参数是函数的参数。
    pthread_create(&tid, nullptr, ThreadRoutine, (void*)"thread 1");

    //主线程
    while(true)
    {
        std::cout << "i am main thread" << " cnt:" << cnt << " &cnt:" << &cnt << std::endl;
        sleep(1);
    }

    return 0;
}

可以看到cnt被两个线程共享了,新线程一改,主线程立马就能看到。

相比于进程间通信,两个执行流先通过OS让两个进程看到同一份资源然后才能通信。多线程这里让两个执行流看到同一份资源非常简单,直接定义一个全局变量,两个线程就都能看到了。

所以线程间通信的成本要比进程间通信低很多。

再谈谈线程为什么更轻量化?

线程间切换的时候,地址空间和页表这些是不用切换的,只需要切换一些运行中产生的临时数据相关的寄存器。不用切换CPU中所有寄存器。上下文切换的成本更低,但这不是轻量化的主要矛盾。

我们查一下CPU的信息

cat /proc/cpuinfo

CPU中存在一个硬件存储空间cache缓存。

举个例子:当我们访问main函数的第十行代码时,接下来有较大的概率访问第11行第12行代码,当我们正在访问某个地址的数据时,接下来也有较大的概率继续访问这个数据。这种客观现象我们称为局部性原理。

当我们加载一个较大的可执行程序时,我们会先加载一部分到内存,但加载哪一部分呢?

局部性原理为预加载机制提供理论基础。根据局部性原理,有大概率加载我们需要的,如果加载错了再重新加载就行。这提高了IO效率。

我们把cache中的数据称为热数据,加载一部分数据和代码到cache中,CPU就不用再访存了,如果cache不命中,令cache中的数据失效,重新加载一批数据到cache中即可。

线程间切换是不需要更新cache内容的,因为概率上下一个线程也有较大概率高频,立即访问热数据。但是进程间切换A进程的代码和数据对B进程没有意义,cache会失效,需要重新缓存。

所以线程间切换为什么效率高?

1.切换的寄存器少。

2.不需要重新更新cache。

时间片也是资源,要被内部的线程瓜分。

 

线程并不是越多效率越高,对于计算密集型,假设计算机有两个八核CPU,最好创建16个线程并发执行,如果只有一个单核CPU,一个线程的效率是最高的,因为多个线程还要切换上下文资源,降低效率。对于IO密集型,因为线程访问IO可能会阻塞,创建多个线程可以使等待时间重合,减少总的等待时间,提高效率。

线程缺点:

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。

除了地址空间被线程共享外,还有以下资源被共享

线程要有自己独立的栈结构和硬件上下文数据。

因为第四个参数是void*类型,所以也可以传递对象。

试一下传递对象

#include <iostream>
#include <pthread.h>
#include <functional>
#include <time.h>
#include <unistd.h>


using func_t = std::function<void()>;

class ThreadData
{
public:
    ThreadData(const std::string & name, const uint64_t time, func_t f)
        :threadname(name), createtime(time), func(f)
    {}

    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout << "我是线程执行任务的一部分" << std::endl;
}

//新线程
void *ThreadRountine(void *args)
{
    ThreadData *td = static_cast<ThreadData*>(args); //安全强转
    while(true)
    {
        std::cout << "thread name:" << td->threadname << " create time:" << td->createtime << std::endl;
        td->func();
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    ThreadData *td = new ThreadData("thread 1", (uint64_t)time(nullptr), Print);
    pthread_create(&tid, nullptr, ThreadRountine, (void*)td);

    while(true)
    {
        std::cout << "main thread" << std::endl;
        sleep(3);
    }

    return 0;
}

三、创建多线程

#include <iostream>
#include <pthread.h>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <vector>


using func_t = std::function<void()>;
const int threadnum = 5;

class ThreadData
{
public:
    ThreadData(const std::string & name, const uint64_t time, func_t f)
        :threadname(name), createtime(time), func(f)
    {}

    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout << "我是线程执行任务的一部分" << std::endl;
}

//新线程
void *ThreadRountine(void *args)
{
    ThreadData *td = static_cast<ThreadData*>(args); //安全强转
    while(true)
    {
        std::cout << "thread name:" << td->threadname << " create time:" << td->createtime << std::endl;
        td->func();
        sleep(1);
    }
}

int main()
{
    std::vector<pthread_t> pthreads;
    for(size_t i = 0; i < threadnum; i++)
    {
        char threadname[64];
        snprintf(threadname, sizeof(threadname), "thread-%lu", i);

        pthread_t tid;
        ThreadData *td = new ThreadData(threadname, (uint64_t)time(nullptr), Print);
        pthread_create(&tid, nullptr, ThreadRountine, (void *)td);
        pthreads.push_back(tid);
        sleep(1);
    }

    while(true)
    {
        std::cout << "main thread" << std::endl;
        sleep(3);
    }

    return 0;
}

当一个线程出异常,整个进程会退出

#include <iostream>
#include <pthread.h>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <vector>


using func_t = std::function<void()>;
const int threadnum = 5;

class ThreadData
{
public:
    ThreadData(const std::string & name, const uint64_t time, func_t f)
        :threadname(name), createtime(time), func(f)
    {}

    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout << "我是线程执行任务的一部分" << std::endl;
}

//新线程
void *ThreadRountine(void *args)
{
    int a = 10;
    ThreadData *td = static_cast<ThreadData*>(args); //安全强转
    while(true)
    {
        std::cout << "thread name:" << td->threadname << " create time:" << td->createtime << std::endl;
        td->func();
        if(td->threadname == "thread-4")
        {
            std::cout << "thread-4触发了异常" << std::endl;
            a /= 0; //设置除零异常
        }
        sleep(1);
    }
}

int main()
{
    std::vector<pthread_t> pthreads;
    for(size_t i = 0; i < threadnum; i++)
    {
        char threadname[64];
        snprintf(threadname, sizeof(threadname), "thread-%lu", i);

        pthread_t tid;
        ThreadData *td = new ThreadData(threadname, (uint64_t)time(nullptr), Print);
        pthread_create(&tid, nullptr, ThreadRountine, (void *)td);
        pthreads.push_back(tid);
        sleep(1);
    }

    while(true)
    {
        std::cout << "main thread" << std::endl;
        sleep(3);
    }

    return 0;
}

为什么一个退出,整个全退了呢?

除零异常收到了8号信号

每种信号的处理方式是共享的,一个线程一旦崩溃了,其他线程会执行同样的方法。

输出线程tid,发现和LWP不一样

#include <iostream>
#include <pthread.h>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <vector>


using func_t = std::function<void()>;
const int threadnum = 5;

class ThreadData
{
public:
    ThreadData(const std::string & name, const uint64_t time, func_t f)
        :threadname(name), createtime(time), func(f)
    {}

    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout << "我是线程执行任务的一部分" << std::endl;
}

//新线程
void *ThreadRountine(void *args)
{
    // int a = 10;
    ThreadData *td = static_cast<ThreadData*>(args); //安全强转
    while(true)
    {
        std::cout << "thread name:" << td->threadname << " create time:" << td->createtime << std::endl;
        td->func();
        // if(td->threadname == "thread-4")
        // {
        //     std::cout << "thread-4触发了异常" << std::endl;
        //     a /= 0; //设置除零异常
        // }
        sleep(1);
    }
}

int main()
{
    std::vector<pthread_t> pthreads;
    for(size_t i = 0; i < threadnum; i++)
    {
        char threadname[64];
        snprintf(threadname, sizeof(threadname), "thread-%lu", i);

        pthread_t tid;
        ThreadData *td = new ThreadData(threadname, (uint64_t)time(nullptr), Print);
        pthread_create(&tid, nullptr, ThreadRountine, (void *)td);
        pthreads.push_back(tid);
        sleep(1);
    }

    for(auto e : pthreads)
    {
        std::cout << e << ' ';
    }
    std::cout << std::endl;

    while(true)
    {
        std::cout << "main thread" << std::endl;
        sleep(3);
    }

    return 0;
}

所以线程id是什么意思?

pthread_self获取调用线程的id,也就是自身的id

我们把线程id转化为16进制打印出来

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>


std::string ToHex(pthread_t id)
{
    char Hex[64];
    snprintf(Hex, sizeof(Hex), "0x%lx", id);
    return Hex;
}

void *Func(void *args)
{
    while(true)
    {
        sleep(1);
        std::cout << "new thread id:" << ToHex(pthread_self()) << std::endl;
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, nullptr);

    while(true)
    {
        std::cout << "main thread id:" << ToHex(pthread_self()) << std::endl;
        sleep(1);
    }
    return 0;
}

thread id本质是一个地址 //???

四、线程退出

当线程不同情况下退出,进程的不同响应。

1.新线程代码执行完毕正常退出,主线程照常运行,进程正常。

2.新线程使用exit退出,进程也直接退出

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>


std::string ToHex(pthread_t id)
{
    char Hex[64];
    snprintf(Hex, sizeof(Hex), "0x%lx", id);
    return Hex;
}

void *Func(void *args)
{
    usleep(1000);
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "new thread id:" << ToHex(pthread_self()) << std::endl;
        sleep(1);
    }
    //return nullptr; //线程终止
    exit(13); //进程终止
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, nullptr);

    while(true)
    {
        std::cout << "main thread id:" << ToHex(pthread_self()) << std::endl;
        sleep(1);
    }
    return 0;
}

pthread_exit终止线程

这个和return nullptr都返回了void*

pthread_exit(nullptr);

pthread_join进行线程等待

int n = pthread_join(tid, nullptr); //等待成功返回0

关于第二个参数retval

void类型不能定义变量,因为void要定义多少字节是不确定的。

线程正常返回时获取线程的返回值

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>


std::string ToHex(pthread_t id)
{
    char Hex[64];
    snprintf(Hex, sizeof(Hex), "0x%lx", id);
    return Hex;
}

void *Func(void *args)
{
    usleep(1000);
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "new thread id:" << ToHex(pthread_self()) << std::endl;
        sleep(1);
    }
    //return nullptr; //线程终止
    //exit(13); //进程终止
    //pthread_exit(nullptr);
    return (void*)"new thread done"; //返回这个字符串常量的起始地址
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, nullptr);

    std::cout << "main thread id:" << ToHex(pthread_self()) << std::endl;

    //线程默认要被等待
    //线程退出,没有等待,会导致类似进程的僵尸问题,造成内存泄露
    void *ret = nullptr;
    int n = pthread_join(tid, &ret); //等待成功返回0
    std::cout << "main thread done" << " n: " << n << std::endl;
    std::cout << "main thread get new thread return: " << (const char*)ret << std::endl;

    return 0;
}

如果线程出异常,也不需要处理,因为线程出异常,主线程也跟着挂了,pthread_join可能连返回的机会都没有。

因为返回类型是void*,所以可以返回一个结构体对象

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>


std::string ToHex(pthread_t id)
{
    char Hex[64];
    snprintf(Hex, sizeof(Hex), "0x%lx", id);
    return Hex;
}

class ThreadReturn
{
public:
    ThreadReturn(pthread_t id, const std::string &info, int code)
        :_id(id), _info(info), _code(code)
    {}
public:
    pthread_t _id;
    std::string _info;
    int _code;
};

void *Func(void *args)
{
    usleep(1000);
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "new thread id:" << ToHex(pthread_self()) << std::endl;
        sleep(1);
    }
    //return nullptr; //线程终止
    //exit(13); //进程终止
    //pthread_exit(nullptr);
    //return (void*)"new thread done"; //返回这个字符串常量的起始地址
    ThreadReturn *ret = new ThreadReturn(pthread_self(), "new thread done", 10);
    return ret; //返回一个对象
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, nullptr);

    std::cout << "main thread id:" << ToHex(pthread_self()) << std::endl;

    //线程默认要被等待
    //线程退出,没有等待,会导致类似进程的僵尸问题,造成内存泄露
    void *ret = nullptr;
    int n = pthread_join(tid, &ret); //等待成功返回0
    std::cout << "main thread done" << " n: " << n << std::endl;

    ThreadReturn *r = static_cast<ThreadReturn *>(ret);
    //std::cout << "main thread get new thread return: " << (const char*)ret << std::endl;
    std::cout << "main thread get new thread return, id:" << ToHex(r->_id) << " info:" << r->_info << " code:" << r->_code << std::endl;
    delete r;

    return 0;
}

pthread_detach进行线程分离

可以由主线程分离新线程,也可以新线程自己分离自己

#include <iostream>
#include <pthread.h>
#include <unistd.h>


void *Func(void *args)
{
    pthread_detach(pthread_self());
    
    int cnt = 4;
    while(cnt--)
    {
        std::cout << "i am a new thread" << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, (void *)"thread-1");

    //pthread_detach(tid);
    sleep(1); //要保证Func中detach比下面的join先执行
    int n = pthread_join(tid, nullptr);
    std::cout << "n:" << n << std::endl;

    return 0;
}

pthread_cancel取消一个线程

#include <iostream>
#include <pthread.h>
#include <unistd.h>


void *Func(void *args)
{
    //pthread_detach(pthread_self());
    while(true)
    {
        std::cout << "i am a new thread" << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, (void *)"thread-1");
    sleep(5);
    //pthread_detach(tid);
    //sleep(1); //要保证Func中detach比下面的join先执行

    int n = pthread_cancel(tid); //取消成功返回0
    std::cout << "pthread_cancel n:" << n << std::endl;

    void *ret = nullptr;
    n = pthread_join(tid, &ret);
    std::cout << "pthread_join n:" << n << " thread return:" << (int64_t)ret << std::endl;

    return 0;
}

可以看到取消线程之后,pthread_join那里不会阻塞,并且得到的返回值是-1

如果线程先被分离了,再进行取消,等待呢?

#include <iostream>
#include <pthread.h>
#include <unistd.h>


void *Func(void *args)
{
    //pthread_detach(pthread_self());
    while(true)
    {
        std::cout << "i am a new thread" << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, (void *)"thread-1");
    sleep(5);
    pthread_detach(tid); //线程分离
    pthread_detach(tid); //线程取消
    //sleep(1); //要保证Func中detach比下面的join先执行

    int n = pthread_cancel(tid); //取消成功返回0
    std::cout << "pthread_cancel n:" << n << std::endl;

    void *ret = nullptr;
    n = pthread_join(tid, &ret); //线程等待
    std::cout << "pthread_join n:" << n << " thread return:" << (int64_t)ret << std::endl;

    return 0;
}

线程被分离后,可以取消,不能被等待

如果线程被cancel掉了,join得到的线程的返回值是一个宏值

#define PTHREAD_CANCEL ((void *) -1)

五、Linux中的线程称为用户级线程

创建轻量级进程的方法:clone(也是fork的底层)

我们使用pthread_create方法也是调用clone方法

参数一是要执行的函数方法,flags表示要创建一个子进程还是一个轻量级进程,arg是参数。

pthread库中会new一段堆空间,把堆空间的地址传进来充当child_stack栈空间。

所以每个新线程的栈在库中维护,动态库一般被加载到共享区中,当我们创建一个线程时,pthread库在堆中new一段空间,把堆空间的地址写到库里面,在库里面用指针维护它,在线程运行用到的一些栈空间通过这个指针就可以找到了。

在有许多线程之前,一定先有一个主线程的,默认地址空间中的栈由主线程使用。 

C++11内部的多线程本质就是对原生线程库的封装。

关于线程的局部存储

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>

// int g_val = 100; //全局变量,本身就是被所有线程共享的。
__thread int g_val = 100; //__thread是一个编译选项,线程的局部存储,每个线程独有一份

void *Func(void *args)
{
    std::string name = static_cast<const char *>(args);
    while(true)
    {
        sleep(1);
        std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n" << std::endl;
        g_val++;
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Func, (void *)"thread-1");

    while(true)
    {
        sleep(1);
        std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n" << std::endl;
    }
    pthread_join(tid, nullptr);

    return 0;
}

主线程的g_val不变,新线程的g_val不断++

__thread std::string threadname; //只能用整型,不能用string等容器
//因为__thread是一个编译选项,在编译就给每份进程进行拷贝了

  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值