Linux多线程(下)

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【Linux的学习】
📝📝本篇内容:信号量;基于环形队列的生产消费模型;线程池;STL和线程安全;各种锁;读者写者问题
⬆⬆⬆⬆上一篇:Linux多线程(中)
💖💖作者简介:轩情吖,请多多指教(>> •̀֊•́ ) ̖́-

1.信号量

1.1基本概念

信号量,又名信号灯,本质上是一个计数器

信号量需要进行PV操作,P(–),V(++),两者都是原子性的
当信号量从1到0再到1时,只有两种状态,称为二元信号量,它等同于互斥锁
信号量是用来描述临界资源中资源数目的
还有一种叫做多元信号量
每一个线程,在访问对应的资源的时候,先申请信号量,申请成功则表示该线程允许使用该资源,申请不成功,表示目前无法使用该资源
信号量的工作机制是一种资源的预订机制
信号量已经是资源的计数器了,申请信号量成功本身就表明资源可用。申请信号量失败表明资源不可用,本质上把判断转换成为信号量的申请行为。
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源的目的,但POSIX可以用于线程间同步

1.2信号量函数

在这里插入图片描述
参数:
pshared:0表示线程间共享,非0表示进程间共享
value:信号量初始值
在这里插入图片描述
在这里插入图片描述
sem_wait()函数就是用来实现P自减的操作,等待信号量,会将信号量的值减1
在这里插入图片描述
sem_post函数就是用来实现V自增操作的,表示发布信号量,表示资源使用完毕,可以归还资源了,将信号量值加1
具体使用看下面环形队列的代码

2.基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环形特性
环形结构起始状态和结束状态都是一模一样的,不好判断是空是满,所以可以通过计数器或者标记位来判断满或空,另外也可以预留一个空的位置,作为满状态
现在可以通过信号量这个计数器
在这里插入图片描述
消费者向tail中push数据;消费者向head中pop数据

2.1构建CP问题

①生产者关心的是空间,消费者关心的是数据
②只要信号不为0,表示资源可用,表示线程可访问
③环形队列只要我们访问不同的区域,生产和消费可以同时进行
④消费者生产者访问同一个区域:消费者和生产者刚开始的时候,没有数据,为空时,指向同一个位置,存在竞争关系,生产者先运行;数据满时,消费者先运行
⑤只要为空和为满时,cp才会指向同一个位置,其他情况,cp可以并发运行
⑥我们要保证这个规则,同时也要保证空或者满时候的策略问题:不能让生产者套圈消费者;不能让消费者超过生产者

2.2示例代码

//RingQueue.hpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <semaphore.h>
using namespace std;
#define SIZE 5
template<class T>
class RingQueue
{
    public:
        RingQueue()
        :_ring(SIZE)
        {
            sem_init(&_customer,0,0);
            sem_init(&_productor,0,_capacity);
            _pos_customer=_pos_productor=0;
            pthread_mutex_init(&_mutex_customer,nullptr);
            pthread_mutex_init(&_mutex_productor,nullptr);
        }
		
    	~RingQueue()
   		 {
        	sem_destroy(&_customer);
       		sem_destroy(&_productor);

        	pthread_mutex_destroy(&_mutex_customer);
       		pthread_mutex_destroy(&_mutex_productor);
    	}

        void push(const T& data)
        {
            sem_wait(&_productor);//等待信号量,如果有就申请成功
            pthread_mutex_lock(&_mutex_productor);
            _pos_productor%=_capacity;//保证环形队列
            _ring[_pos_productor++]=data;
            pthread_mutex_unlock(&_mutex_productor);
            sem_post(&_customer);//此时有数据了,唤醒消费者
        }

        void pop(T* data)
        {
            sem_wait(&_customer);//如果申请失败,就阻塞等待
            pthread_mutex_lock(&_mutex_customer);
            _pos_customer%=_capacity;
            *data=_ring[_pos_customer++];
            pthread_mutex_unlock(&_mutex_customer);
            sem_post(&_productor);//原子的
        }



    private:
    vector<T> _ring;//环形队列
    sem_t _customer;//消费者的信号量,只有消费者关心的数据
    sem_t _productor;//生产者的信号量,只有生产者关心的空间
    int _pos_customer;//消费位置;保证环形队列的特性而不越界
    int _pos_productor;//生产位置;保证环形队列的特性而不越界
    pthread_mutex_t _mutex_productor;//来限制生产者线程的锁
    pthread_mutex_t _mutex_customer;//来限制消费者线程的锁
    int _capacity=SIZE;//环形队列的大小
};

//main.cc
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include "Task.hpp"
#include "RingQueue.hpp"
using namespace std;
void *ProductorRun(void *arg) // 生产者线程要使用的函数
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(arg);
    while (1)
    {
        // 创建Task任务
        char arr[] = "+-*/%";
        int x = rand() % 10;
        int y = rand() % 20;
        char c = arr[rand() % 5];
        Task t(x, y, c);
        rq->push(t);
        cout << t.to_productor() << endl;
    }

    return nullptr;
}

void *CustomerRun(void *arg) // 消费者线程要使用的函数
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(arg);
    while (1)
    {
        sleep(3);
        Task t;
        rq->pop(&t);
        t();
        cout << t.to_customer() << endl;
    }

    return nullptr;
}

int main()
{
    srand((uint64_t)time(0));
    RingQueue<Task>* rq=new RingQueue<Task>;
    pthread_t customer[3];
    pthread_t productor[3];
    pthread_create(&customer[0],nullptr,CustomerRun,rq);
    pthread_create(&customer[1],nullptr,CustomerRun,rq);
    pthread_create(&customer[2],nullptr,CustomerRun,rq);
    pthread_create(&productor[0],nullptr,ProductorRun,rq);
    pthread_create(&productor[1],nullptr,ProductorRun,rq);
    pthread_create(&productor[2],nullptr,ProductorRun,rq);
    
    //join
    pthread_join(customer[0],nullptr);
    pthread_join(customer[1],nullptr);
    pthread_join(customer[2],nullptr);
    pthread_join(productor[0],nullptr);
    pthread_join(productor[1],nullptr);
    pthread_join(productor[2],nullptr);
    



    return 0;
}

//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
//环形队列中的内容,生产者生产,消费者消费
class Task
{
    public:
    Task()
    {}

    Task(int x,int y,char op)
    :_x(x),_y(y),_op(op)
    {}

    void operator()()
    {
        switch(_op)
        {
            case '+':
                    _result=_x+_y;
                    break;
            case '-':
                    _result=_x-_y;
                    break;
            case '/':
                    if(_y==0)
                    {
                        _code=-1;
                        break;
                    }
                    _result=_x/_y;
                    break;
            case '*':
                    _result=_x*_y;
                    break;
            case '%':
                    if(_y==0)
                    {
                        _code=-2;
                        break;
                    }
                    _result=_x%_y;
                    break;
        default:
                _code=-3;
                break;
        }
    }

    string to_productor()//打印productor生产的内容是什么
    {
        return to_string(_x)+_op+to_string(_y)+"=?";
    }

    string to_customer()//打印customer消费后的结果为什么
    {
        return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
    }



    private:
    int _x;//操作数
    int _y;//操作数
    char _op;//计算方法
    int _result=0;//计算结果
    int _code=0;//计算后的返回码
};


//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
# makefile
main:main.cc
	g++ -o main main.cc -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -rf main

在这里插入图片描述

根据对应的临界资源是否被整体使用来确定用锁还是sem

3.线程池

一种线程的使用模式,线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监管者分配可并发执行的任务,这避免了在处理短时间任务时创建和销毁线程的代价,线程池不仅能够保证内核的充分利用,还能防止过分调度
我这边一共演示了四个版本:

V1:此版本的线程池是使用的是系统的pthread

//main.cc
#include "ThreadPoolV1.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
     srand((uint64_t)time(0));
    unique_ptr<ThreadPool<Task>> pool(new ThreadPool<Task>);
    pool->Start();
    while(1)
    {
        int x=rand()%10;
        int y=rand()%20;
        char arr[]="+-*/%";
        char op=arr[rand()%strlen(arr)];
        pool->push(Task(x,y,op));
        // sleep(1);
    }

    return 0;
}
//ThreadPoolV1.hpp
//此版本的线程池是使用的是系统的pthread
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
using namespace std;
#define SIZE 5
template<class T>
class ThreadPool
{
    public:
    ThreadPool(int size=SIZE)
    :_pool(size),//提前把空间开好,后面就可以直接访问了而不会越界访问
    _capacity(size)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
    }
    
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        
    }

    void push(const T& task)
    {
        pthread_mutex_lock(&_mutex);
        _task.push(task);
        pthread_cond_signal(&_cond);//唤醒线程来执行task
        pthread_mutex_unlock(&_mutex);
       
    }


    static void* Routine(void* arg)
    {
        ThreadPool* tp=static_cast<ThreadPool*>(arg);
        while(1)
        {
            //所有的线程都在这里面运行,省去了创建和销毁的开销
            pthread_mutex_lock(&tp->_mutex);
            while(tp->_task.empty())//使用while而不是if可以更安全,即使意外唤醒了
            {
                pthread_cond_wait(&tp->_cond,&tp->_mutex);
            }
            T t=tp->_task.front();//获取任务
            tp->_task.pop();
            pthread_mutex_unlock(&tp->_mutex);
            t();//完成任务
            cout<<t.to_customer()<<endl;
        }
    }


    void Start()
    {
        for(int i=0;i<_capacity;i++)
        {
        pthread_create(&_pool[i],nullptr,Routine,this);
        }

    }



    private:
    vector<pthread_t> _pool;//线程池
    queue<T> _task;//任务
    int _capacity;//线程池的大小
    pthread_mutex_t _mutex;//保护task的资源
    pthread_cond_t _cond;//在没有task时进行一个等待
};
//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
//阻塞队列中的内容,生产者生产,消费者消费
class Task
{
    public:
    Task()
    {}

    Task(int x,int y,char op)
    :_x(x),_y(y),_op(op)
    {}

    void operator()()
    {
        switch(_op)
        {
            case '+':
                    _result=_x+_y;
                    break;
            case '-':
                    _result=_x-_y;
                    break;
            case '/':
                    if(_y==0)
                    {
                        _code=-1;
                        break;
                    }
                    _result=_x/_y;
                    break;
            case '*':
                    _result=_x*_y;
                    break;
            case '%':
                    if(_y==0)
                    {
                        _code=-2;
                        break;
                    }
                    _result=_x%_y;
                    break;
        default:
                _code=-3;
                break;
        }
    }

    string to_productor()//打印productor生产的内容是什么
    {
        return to_string(_x)+_op+to_string(_y)+"=?";
    }

    string to_customer()//打印customer消费后的结果为什么
    {
        return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
    }



    private:
    int _x;//操作数
    int _y;//操作数
    char _op;//计算方法
    int _result=0;//计算结果
    int _code=0;//计算后的返回码
};


//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
# makefile
main:main.cc
	g++ -o main main.cc -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -rf main

在这里插入图片描述

V2:此版本的线程池是使用的是自己写的Thread对象,对pthread_t进行了封装

//main.cc
// #include "ThreadPoolV1.hpp"
#include "ThreadPoolV2.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
     srand((uint64_t)time(0));
    unique_ptr<ThreadPool<Task>> pool(new ThreadPool<Task>);
    pool->Init();
    pool->Start();
    pool->Check();
    while(1)
    {
        int x=rand()%10;
        int y=rand()%20;
        char arr[]="+-*/%";
        char op=arr[rand()%strlen(arr)];
        pool->push(Task(x,y,op));
        sleep(1);
    }

    return 0;
}
// ThreadPoolV2.hpp
// 此版本的线程池是使用的是自己写的Thread对象
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
#include "Thread.hpp"
using namespace std;
#define SIZE 5
template <class T>
class ThreadPool
{
public:
    ThreadPool(int size = SIZE)
        : //这里不用把线程池空间先开好,不然会出现没有默认构造函数问题
          _capacity(size)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    ~ThreadPool()
    {
         for(int i=0;i<_capacity;i++)
        {
                _pool[i].Join();
        }
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

     void Init() // 进行一个初始化,把我们的一个Thread对象放进线程池
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool.push_back(Thread(i, Routine, this));
        }
    }

    void Start() // 我们的线程真正开始执行起来
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool[i].Run(); // 调用Thread对象的Run函数
        }
    }


    void push(const T &task)
    {
        pthread_mutex_lock(&_mutex);
        _task.push(task);
        pthread_cond_signal(&_cond); // 唤醒线程来执行task
        pthread_mutex_unlock(&_mutex);
    }

    static void *Routine(void *arg)
    {
        ThreadPool *tp = static_cast<ThreadPool *>(arg);
        while (1)
        {
            // 所有的线程都在这里面运行,省去了创建和销毁的开销
            pthread_mutex_lock(&tp->_mutex);
            while (tp->_task.empty()) // 使用while而不是if可以更安全,即使意外唤醒了
            {
                pthread_cond_wait(&tp->_cond, &tp->_mutex);
            }
            T t = tp->_task.front(); // 获取任务
            tp->_task.pop();
            pthread_mutex_unlock(&tp->_mutex);
            t(); // 完成任务
            cout << t.to_customer() << endl;
        }
    }
   

    void Check()
    {
        for (int i = 0; i < _capacity; i++)
        {
            cout << _pool[i].ThreadName() << endl;
        }
    }

private:
    vector<Thread> _pool;   // 线程池
    queue<T> _task;         // 任务
    int _capacity;          // 线程池的大小
    pthread_mutex_t _mutex; // 保护task的资源
    pthread_cond_t _cond;   // 在没有task时进行一个等待
};
//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
class Task
{
    public:
    Task()
    {}

    Task(int x,int y,char op)
    :_x(x),_y(y),_op(op)
    {}

    void operator()()
    {
        switch(_op)
        {
            case '+':
                    _result=_x+_y;
                    break;
            case '-':
                    _result=_x-_y;
                    break;
            case '/':
                    if(_y==0)
                    {
                        _code=-1;
                        break;
                    }
                    _result=_x/_y;
                    break;
            case '*':
                    _result=_x*_y;
                    break;
            case '%':
                    if(_y==0)
                    {
                        _code=-2;
                        break;
                    }
                    _result=_x%_y;
                    break;
        default:
                _code=-3;
                break;
        }
    }

    string to_productor()//打印productor生产的内容是什么
    {
        return to_string(_x)+_op+to_string(_y)+"=?";
    }

    string to_customer()//打印customer消费后的结果为什么
    {
        return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
    }



    private:
    int _x;//操作数
    int _y;//操作数
    char _op;//计算方法
    int _result=0;//计算结果
    int _code=0;//计算后的返回码
};


//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
//Thread.hpp
//自己写的Thread对象
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <functional>
using namespace std;
using func_t=function<void*(void*)>;
enum Status
{
    NEW,
    RUNNING,
    EXIT,
};


class Thread
{
    public:
    Thread(int num,func_t func,void* arg)
    :_func(func),
    _arg(arg)
    {
        char buff[1024];
        snprintf(buff,sizeof(buff),"thread-%d",num);
        _name+=buff;
        _status=NEW;
    }


    void Run()
    {
        if(0!=pthread_create(&_tid,nullptr,RunHelper,this))
        //这边又封装了一层,来让我们线程控制的函数能够让我们进行控制,为后续的线程池做铺垫
        {
            exit(-1);
        }
        _status=RUNNING;
    }

    static void* RunHelper(void* arg)
    {
       Thread* td=static_cast<Thread*>(arg);
        (*td)();//这边也封装了一层
        
    }

    void* operator()()
    {
        return _func(_arg);
    }


    void Join(void)
    {
        int n=pthread_join(_tid,nullptr);
          if(n!=0)
        {
            exit(-2);
        }
        _status=EXIT;
    }

    string ThreadName()
    {
        return _name;
    }

    pthread_t ThreadId()
    {
        if(_status==RUNNING)
        {
        return _tid;
        }
        else
        {
            return 0;
        }
    }


    private:
    string _name;//线程名
    pthread_t _tid;//线程id
    Status _status;//线程当前的一个状态 
    func_t  _func;//线程执行的函数
    void* _arg;//传给线程执行的函数参数
};


//退出码:
//-1-> pthread_create error
//-2-> pthread_join error
# makefile
main:main.cc
	g++ -o main main.cc -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -rf main

在这里插入图片描述

V3:此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII

//main.cc
// #include "ThreadPoolV1.hpp"
//#include "ThreadPoolV2.hpp"
#include "ThreadPoolV3.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
     srand((uint64_t)time(0));
    unique_ptr<ThreadPool<Task>> pool(new ThreadPool<Task>);
    pool->Init();
    pool->Start();
    pool->Check();
    while(1)
    {
        int x=rand()%10;
        int y=rand()%20;
        char arr[]="+-*/%";
        char op=arr[rand()%strlen(arr)];
        pool->push(Task(x,y,op));
        sleep(1);
    }

    return 0;
}
// ThreadPoolV3.hpp
// 此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
#include "Thread.hpp"
#include "Lock.hpp"
using namespace std;
#define SIZE 5
template <class T>
class ThreadPool
{
public:
    ThreadPool(int size = SIZE)
        : // 这里不用把线程池空间先开好,不然会出现没有默认构造函数问题
          _capacity(size)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    ~ThreadPool()
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool[i].Join();
        }
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    void Init() // 进行一个初始化,把我们的一个Thread对象放进线程池
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool.push_back(Thread(i, Routine, this));
        }
    }

    void Start() // 我们的线程真正开始执行起来
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool[i].Run(); // 调用Thread对象的Run函数
        }
    }

    void push(const T &task)
    {
        Lock_Guard m(&_mutex);//使用RAII
        // pthread_mutex_lock(&_mutex);
        _task.push(task);
        pthread_cond_signal(&_cond); // 唤醒线程来执行task
        // pthread_mutex_unlock(&_mutex);
    }

    static void *Routine(void *arg)
    {
        ThreadPool *tp = static_cast<ThreadPool *>(arg);
        while (1)
        {
            T t;
            // 所有的线程都在这里面运行,省去了创建和销毁的开销
            {
                Lock_Guard m(&tp->_mutex);//通过加上括号来实现RAII
                // pthread_mutex_lock(&tp->_mutex);
                while (tp->_task.empty()) // 使用while而不是if可以更安全,即使意外唤醒了
                {
                    pthread_cond_wait(&tp->_cond, &tp->_mutex);
                }
                t = tp->_task.front(); // 获取任务
                tp->_task.pop();
            }
            // pthread_mutex_unlock(&tp->_mutex);
            t(); // 完成任务
            cout << t.to_customer() << endl;
        }
    }

    void Check()
    {
        for (int i = 0; i < _capacity; i++)
        {
            cout << _pool[i].ThreadName() << endl;
        }
    }

private:
    vector<Thread> _pool;   // 线程池
    queue<T> _task;         // 任务
    int _capacity;          // 线程池的大小
    pthread_mutex_t _mutex; // 保护task的资源
    pthread_cond_t _cond;   // 在没有task时进行一个等待
};
//Lock.hpp
//对锁进行一个封装,RAII
#pragma once
#include <pthread.h>
#include <iostream>
using namespace std;
class Lock_Guard
{
    public:
    Lock_Guard(pthread_mutex_t* mutex)
    :_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    //构造函数的时候加锁,析构函数的时候解锁
    ~Lock_Guard()
    {
        pthread_mutex_unlock(_mutex);
    }

    private:
    pthread_mutex_t* _mutex;
};

其余的跟上一个版本一样
在这里插入图片描述

V4:此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII;同时增加单例模式;也是最后的完整代码

//main.cc
// #include "ThreadPoolV1.hpp"
//#include "ThreadPoolV2.hpp"
//#include "ThreadPoolV3.hpp"
#include "ThreadPoolV4.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
    ThreadPool<Task>* pool=ThreadPool<Task>::GetInstance();
     srand((uint64_t)time(0));
    pool->Init();
    pool->Start();
    pool->Check();
    while(1)
    {
        int x=rand()%10;
        int y=rand()%20;
        char arr[]="+-*/%";
        char op=arr[rand()%strlen(arr)];
        pool->push(Task(x,y,op));
        sleep(1);
    }

    return 0;
}


//Lock.hpp
//对锁进行一个封装,RAII
#pragma once
#include <pthread.h>
#include <iostream>
using namespace std;
class Lock_Guard
{
    public:
    Lock_Guard(pthread_mutex_t* mutex)
    :_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    //构造函数的时候加锁,析构函数的时候解锁
    ~Lock_Guard()
    {
        pthread_mutex_unlock(_mutex);
    }

    private:
    pthread_mutex_t* _mutex;
};
//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
class Task
{
    public:
    Task()
    {}

    Task(int x,int y,char op)
    :_x(x),_y(y),_op(op)
    {}

    void operator()()
    {
        switch(_op)
        {
            case '+':
                    _result=_x+_y;
                    break;
            case '-':
                    _result=_x-_y;
                    break;
            case '/':
                    if(_y==0)
                    {
                        _code=-1;
                        break;
                    }
                    _result=_x/_y;
                    break;
            case '*':
                    _result=_x*_y;
                    break;
            case '%':
                    if(_y==0)
                    {
                        _code=-2;
                        break;
                    }
                    _result=_x%_y;
                    break;
        default:
                _code=-3;
                break;
        }
    }

    string to_productor()//打印productor生产的内容是什么
    {
        return to_string(_x)+_op+to_string(_y)+"=?";
    }

    string to_customer()//打印customer消费后的结果为什么
    {
        return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
    }



    private:
    int _x;//操作数
    int _y;//操作数
    char _op;//计算方法
    int _result=0;//计算结果
    int _code=0;//计算后的返回码
};


//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
//Thread.hpp
//自己写的Thread对象
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <functional>
using namespace std;
using func_t=function<void*(void*)>;
enum Status
{
    NEW,
    RUNNING,
    EXIT,
};


class Thread
{
    public:
    Thread(int num,func_t func,void* arg)
    :_func(func),
    _arg(arg)
    {
        char buff[1024];
        snprintf(buff,sizeof(buff),"thread-%d",num);
        _name+=buff;
        _status=NEW;
    }


    void Run()
    {
        if(0!=pthread_create(&_tid,nullptr,RunHelper,this))
        //这边又封装了一层,来让我们线程控制的函数能够让我们进行控制,为后续的线程池做铺垫
        {
            exit(-1);
        }
        _status=RUNNING;
    }

    static void* RunHelper(void* arg)
    {
       Thread* td=static_cast<Thread*>(arg);
        (*td)();//这边也封装了一层
        
    }

    void* operator()()
    {
        return _func(_arg);
    }


    void Join(void)
    {
        int n=pthread_join(_tid,nullptr);
          if(n!=0)
        {
            exit(-2);
        }
        _status=EXIT;
    }

    string ThreadName()
    {
        return _name;
    }

    pthread_t ThreadId()
    {
        if(_status==RUNNING)
        {
        return _tid;
        }
        else
        {
            return 0;
        }
    }


    private:
    string _name;//线程名
    pthread_t _tid;//线程id
    Status _status;//线程当前的一个状态 
    func_t  _func;//线程执行的函数
    void* _arg;//传给线程执行的函数参数
};


//退出码:
//-1-> pthread_create error
//-2-> pthread_join error
// ThreadPoolV4.hpp
// 此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII;同时增加单例模式
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
#include "Thread.hpp"
#include "Lock.hpp"
using namespace std;
#define SIZE 5
template <class T>
class ThreadPool
{  
private:
    ThreadPool(int size = SIZE)
        : // 这里不用把线程池空间先开好,不然会出现没有默认构造函数问题
          _capacity(size)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    ThreadPool(const ThreadPool&)=delete;
    ThreadPool& operator=(const ThreadPool&)=delete;
    //上述的操作保证只能通过GetInstance来创建对象->单例模式
public:
    static ThreadPool<T>* GetInstance()
    {
        if(_ptr==nullptr)//加上来减少锁的一个消耗
        {
        Lock_Guard m(&_instance_mutex);
        if(_ptr==nullptr)
        {
            _ptr=new ThreadPool<T>;
            return _ptr;
        }
        }
        return _ptr;
    }

    ~ThreadPool()
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool[i].Join();
        }
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    void Init() // 进行一个初始化,把我们的一个Thread对象放进线程池
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool.push_back(Thread(i, Routine, this));
        }
    }

    void Start() // 我们的线程真正开始执行起来
    {
        for (int i = 0; i < _capacity; i++)
        {
            _pool[i].Run(); // 调用Thread对象的Run函数
        }
    }

    void push(const T &task)
    {
        Lock_Guard m(&_mutex);//使用RAII
        // pthread_mutex_lock(&_mutex);
        _task.push(task);
        pthread_cond_signal(&_cond); // 唤醒线程来执行task
        // pthread_mutex_unlock(&_mutex);
    }

    static void *Routine(void *arg)
    {
        ThreadPool *tp = static_cast<ThreadPool *>(arg);
        while (1)
        {
             T t;
            // 所有的线程都在这里面运行,省去了创建和销毁的开销
            {
                Lock_Guard m(&tp->_mutex);//通过加上括号来实现RAII
                // pthread_mutex_lock(&tp->_mutex);
                while (tp->_task.empty()) // 使用while而不是if可以更安全,即使意外唤醒了
                {
                    pthread_cond_wait(&tp->_cond, &tp->_mutex);
                }
                t = tp->_task.front(); // 获取任务
                tp->_task.pop();
            }
            // pthread_mutex_unlock(&tp->_mutex);
            t(); // 完成任务
            cout << t.to_customer() << endl;
        }
    }

    void Check()
    {
        for (int i = 0; i < _capacity; i++)
        {
            cout << _pool[i].ThreadName() << endl;
        }
    }

private:
    vector<Thread> _pool;   // 线程池
    queue<T> _task;         // 任务
    int _capacity;          // 线程池的大小
    pthread_mutex_t _mutex; // 保护task的资源
    pthread_cond_t _cond;   // 在没有task时进行一个等待
    static pthread_mutex_t _instance_mutex;//单例模式中使用
    static ThreadPool<T>* _ptr;//单例模式中用到的唯一一个对象
};
template<class T>
ThreadPool<T>* ThreadPool<T>::_ptr=nullptr;
template<class T>
pthread_mutex_t ThreadPool<T>::_instance_mutex=PTHREAD_MUTEX_INITIALIZER;
# makefile
main:main.cc
	g++ -o main main.cc -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -rf main

在这里插入图片描述

4.STL和线程安全

STL中的容器不是线程安全的,这是为了提高性能
智能指针中的unique_ptr不涉及线程安全,而shared_ptr中用到了引用计数变量,会有线程安全,因此基于原子操作(CAS)的方式保证shared_ptr高效

5.各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁,当其他线程想要访问数据时,被挂起阻塞。
乐观锁:每次取数据时,总是乐观的认为数据不会被其他的数据修改,因此不上锁。但是在更新前,会判断其他数据在更新前有没有对数据进行修改,主要采用两种方式:版本号机制和CAS操作
自旋锁:不停地轮询而不是挂起等待,对于是自锁或挂起等待看的是访问临界资源要花费多长时间

6.读者写者问题

321原则:
3:三种关系,读者和读者(没关系);写写(互斥);读写(互斥,同步)
2:读者;写者
1:交易场所
cp的消费者之前是互斥,而rw问题中,读者之间没有关系是因为消费者会拿走数据,而读者不会
注意:写独占,读共享,读锁优先级要高,因此容易导致写者饥饿问题

🌸🌸Linux多线程(下)的知识大概就讲到这里啦,博主后续会继续更新更多Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

轩情吖

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

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

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

打赏作者

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

抵扣说明:

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

余额充值