【Linux】线程的控制

目录

线程等待

线程退出

线程分离

线程的优缺点

线程独占和共享的数据

C++11中的多线程

简单封装


我们说Linux是用进程模拟的线程,所以Linux中只有轻量级进程的概念,但是,用户是只认线程的,所以我们有一个叫原生线程库的东西,它就负责把轻量级进程的系统调用进行封装,转成线程相关的接口提供给用户

为什么叫原生呢?就是说只要你装Linux系统,就必须装这个库,就是默认装好的库。

有关线程的接口在3号手册,就证明它们不是系统调用,因为系统调用在2号手册。当然也有创建轻量级进程的系统调用

man clone,它是在二号手册

我们可以说,其实pthread_create其实就是封装了这个系统调用

pthread_create创建新线程时第一个参数是输出型参数,会带回来一个id值,那么这个值和LWP之间是什么关系呢?我们可以断定的认为它们是一对一的关系,用户层使用id值,内核只用LWP,我们可以打印一下这个id值,我们可以把id值改成十六进制

这个值很大,其实它就是一个地址,位于栈和堆之间的共享区的一个地址,我们后面会有讲解

这是主进程获取新线程的id值,那么一个线程如何获取它自己的id值呢?

man pthread_self

这个函数就是谁调用它就获取谁的id值

我们从主线程和新线程中获取的新线程的id肯定是一样的

新线程和主线程谁先运行呢?这个是不确定的,是由调度器来决定的

pthread_create的第四个参数是给新线程要执行的函数传的参数,是void*类型,我们之前都传的是nullptr,我们可以传一个名字

新主线程大部分资源是共享的,比如地址空间是共享的,我如果定义一个全局变量,那么新主进程都可以看见

我们可以看到,新线程对于全局数据的修改,主线程也是可以看到的

线程等待

主线程如果退出的话,不管新线程是否退出,整个进程都会退出,意味着资源会释放,所有线程都会退出。所以我们往往要求主线程是最后退出的

并且线程也是要被等待的,否则会像进程那样发生内存泄漏的问题,线程等待就要用到下面这个接口

man pthread_join

这个接口也是默认是阻塞式等待

第一个参数是要等待哪个线程

第二个参数是等待到了什么东西,因为新线程的返回值是void*类型的,所以要用void*类型去接收,它是一个输出型参数,所以我要传的参数是void*的指针,所以是void**

我们简单来用一下:

void *newthreadrun(void *args)
{
    char *str = (char *)args;
    int cnt = 5;
    while (cnt--)
    {
        cout << str << " is running " << endl;
        sleep(1);
    }
    return (void *)12345;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, newthreadrun, (void *)"pthread-1");
    void *ret = nullptr;
    int n = pthread_join(tid, &ret);
    if (n == 0)
    {
        cout << " return val is: " << (long long)ret << endl;//指针64位下是8个字节,强转成long long不会损失精度
    }
    return 0;
}

如果说线程出异常(比如除零),那么整个进程都会退出,这是因为信号是给进程发送的,一个线程出了问题,那么就是这个进程出了问题,整个进程都要被杀死。

所以我们普遍认为多进程的代码往往健壮性不好,当然这是普遍来说的,如果一个多线程的代码写的很好,健壮性也是不成问题的

所以pthread_join是不考虑新线程的异常情况的,因为只要异常,整个进程都退出了,根本不存在等待的问题;它只考虑代码运行完,结果是否正确

线程退出

如果对一个线程使用exit会怎样呢?它会使整个进程退出,因为exit本身就是用来终止进程的,而不是用来终止线程的

所以我们有一个pthread_exit用来终止线程

man pthread_exit

参数就是像return后面的数据一样,就是想返回什么

上面这种方法是新线程主动退出,那如果是主线程想让新线程退出该怎么办呢?可以用下面的接口

man pthread_cancel

这里如果取消掉新线程并且等待新线程,那么n==0,ret==-1,这里的-1 其实就是一个宏,表示线程被取消了

所以进程退出有三种方式,return,pthread_exit和pthread_cancel

线程分离

默认新线程是joinable的,就是可等待的,我们也必须等待,否则会出现内存泄漏。但是有时候其实我们并不想管新进程的运行结果,不想让主进程进程阻塞式等待,我们只是想让新进程结束后就自己退出就行了,这时就用到了线程分离,要用到的接口是
man pthread_detach

参数就是传新线程的id就可以了,我们可以在新线程用这个借口,也可以在主线程用这个借口

线程分离其实就是改变线程的一个状态(detached),分离后新线程肯定还是和主线程共享资源的,其他性质都是不变的,如果新线程分离后还等待的话就会出错

无论是线程分离还是之前的SIGCHLD信号,都是让父进程(主线程)不管子进程(新线程),其实一个软件一般就是一个死循环,只要我们不关掉,它就永远不会关,这时主线程其实是无暇去等待新线程的,线程分离也是为了这样的一种场景

线程的优缺点

优点:

1.创建一个新线程的代价比创建一个新进程小很多,因为创建一个线程只需要创建一个PCB,而进程需要地址空间,页表,文件描述表等等

2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

要解释这个,我们就不得不提到CPU中的cache(缓存)了,CPU要执行某行代码,不是只把这行代码放到CPU中,而是要把执行的代码及周围的代码都放到cache中,这样CPU要执行的下一条代码就很有可能已经在缓存中了,对于不同的线程切换来说,这是有几率命中的,但是对于不同的进程切换,肯定要换掉缓存中的代码

我们可以用lscpu看一下云服务器的CPU配置

3.IO操作基本大部分时间都是在等,所以我们可以趁着等的时间多创建几个线程执行其他的任务

缺点:

1.一般计算密集型的进程的线程数量和CPU的数量相同,如果线程数量太多,也会增加调度的开销

2.健壮性降低,线程之间缺乏保护就容易导致产生不良影响

3.编程难度提高,编写与调试一个多线程程序比单线程程序困难得多

线程独占和共享的数据

线程独占:

1.线程的硬件上下文(CPU寄存器内部的值)(从调度角度而言)

2.线程的独立栈结构(从常规运行的角度)

3.线程的ID

4.信号屏蔽字

5.调度优先级

6.errno变量

线程共享

1.函数和全局变量

2.文件描述符表

3.各种信号的处理方式

4.当前工作目录

5.用户id和组id(这些id标识了线程的拥有者和所属组)

C++11中的多线程

c++11也推出了多线程模块,我们只需要包含头文件<thread>就可以创建新线程,我们先来简单的用一下

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void threadrun(int a, int b)
{
    int cnt = 5;
    while (cnt--)
    {
        cout << "a+b=: " << a + b << endl;
        sleep(1);
    }
}
int main()
{
    thread t1(threadrun, 10, 20);
    cout << "I am main thread" << endl;
    t1.join();
    return 0;
}

我们如果不显示链接原生线程库的话就会出现如下的链接错误

这其实就证明C++11的多线程其实就是封装了原生线程库,这跟fopen在Linux下封装了open一样,语言只能封装系统调用才可以修改内核的数据,并且在不同的OS下封装各自OS的系统调用也是为了实现语言的跨平台性。

简单封装

那我们下面就用Linux提供的系统调用封装出类似C++11提供的库中的多线程

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
using namespace std;

namespace MyThread
{
    template<class T>
    using fun_t = function<void(const T &)>;

    template <class T>
    class Thread
    {
    private:
        // using fun_t = function<void(const T &)>;//如果写里边是这样的
        // 等价于 typedef function<void(const T &)> fun_t;
        void excute()
        {
            _func(_data);
        }

    public:
        Thread(fun_t<T> func, const T &data, string &&name)
            : _func(func), _data(data), _stop(true), _name(forward<string>(name)) {}

        static void *threadrun(void *args)//如果不是静态,会有this指针
        {
            Thread<T> *ptr = reinterpret_cast<Thread<T> *>(args);
            ptr->excute();
            return nullptr;
        }

        bool start()
        {
            int n = pthread_create(&_id, nullptr, threadrun, this);//把this当参数传过去
            if (n == 0)
            {
                _stop = false;
                return true;
            }
            else
            {
                return false;
            }
        }
        void join()
        {
            if (!_stop)
            {
                pthread_join(_id, nullptr);
            }
        }
        void detach()
        {
            if (!_stop)
            {
                pthread_detach(_id);
            }
        }
        void stop()
        {
            _stop = true;
        }
        const string &name()
        {
            return _name;
        }

    private:
        pthread_t _id;
        string _name;
        bool _stop;
        fun_t<T> _func;
        T _data;
    };
}

我们要用的话也是非常简单

#include<vector>
#include"MyThread.hpp"
using namespace MyThread;
void print(const int& a)
{
    cout<<"a= "<<a<<endl;
}
int main()
{
    vector<Thread<int>>threads;
    for(int i=1;i<=10;i++)//创建一批线程
    {
        string name="thread-"+to_string(i);
        threads.emplace_back(print,10,name);
    }
    for(auto&e:threads)//启动一批线程
    {
        e.start();
    }
    for(auto&e:threads)//等待一批线程
    {
        e.join();
        cout<<"wait thread done,thread is:"<<e.name()<<endl;
    }

    return 0;
}

我们如果想创建多个线程可以这样

void *newthreadrun(void *args)
{
    char *str = (char *)args;
    int cnt=5;
    while(cnt--)
    {
        cout << str << " is running " << endl;
        sleep(1);
    }
    delete[] str;//new[]的空间要delete[]
    return nullptr;
}
int main()
{
    pthread_t tid;
    vector<pthread_t>f;
    for (int i = 1; i <= 5; i++)
    {
        char*buffer=new char[64];//这里必须new,否则五个线程看到的是一块空间
        snprintf(buffer, 64, "pthread-%d", i);//不能用sizeof,因为sizeof(buffer)==8
        pthread_create(&tid, nullptr, newthreadrun, buffer);
        f.push_back(tid);
    }
    for(auto e:f)
    {
        pthread_join(e,nullptr);
    }

    return 0;
}

我们也可以将线程和C++中的类和对象联系应用起来

template<class T>
T add(T x, T y)
{
    return x + y;
}
template <class T>
class threaddata
{
public:
    threaddata(const char *str, function<T(T, T)> task)
        : _name(str), _task(task)
    {
        delete[] str;
    }
    T Dotask(T x, T y)
    {
        return _task(x, y);
    }
    string _name;
    function<T(T, T)> _task;
    T _result;
};

void *newthreadrun(void *args)
{
    threaddata<int> *p = (threaddata<int> *)args;
    p->_result = p->Dotask(10, 20);
    cout << p->_name << " get result: " << p->_result << endl;
    delete p;
    return nullptr;
}

int main()
{
    pthread_t tid;
    vector<pthread_t> f;
    for (int i = 1; i <= 5; i++)
    {
        char *buffer = new char[64];
        snprintf(buffer, 64, "pthread-%d", i);
        threaddata<int> *ptd = new threaddata<int>(buffer, add<int>);
        pthread_create(&tid, nullptr, newthreadrun, ptd);
        f.push_back(tid);
    }
    for (auto e : f)
    {
        pthread_join(e, nullptr);
    }
    return 0;
}
  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值