读写锁: 为了处理多线程中读数据比写数据更频繁(读多写少),给读加锁会带来效率降低的问题,引入了一种新的锁——读写锁。读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
自旋锁: 对应自旋锁,只有一个线程获得锁资源(与互斥锁),其他未得到锁资源的线程不是挂起等待,而是处于自旋状态,不断去检测锁的状态(自旋锁应用于线程占在临界区内待的时间特别短的场景)
特点:
生产消费模型和读写锁的区别:
读写锁中读者不会拿走数据,但生产消费模型中的消费者会拿走数据,所以读写锁中读者与读者直接是可以共享数据
读写锁的三种同步方案:
读优先: 想尽一切办法让读者先读。当前为读锁,读者可直接进入,为写锁,当前为写锁,写者写完之后,让读者先进入读(可能会造成写饥饿问题)
写优先: 想尽一切办法让写者先写。当前为读锁或写锁,写者都不可以进入,读者读完或写者写完之后,写者可以先进入写
公平占有锁: 读者写者公平竞争锁
线程池: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,简单说:提前把线程创建好,等待任务的派发。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量
线程池的价值:
线程池与进程池:
线程池中首先需要有很多个线程,用户可以自己选择创建多少个线程。为了实现线程间的同步与互斥,还需要增加两个变量——互斥量和条件变量。我们还需要一个任务队列,主线程不断往里面塞任务,线程池的线程不断去处理。需要注意的是:这里的任务队列可以为空,但不能满,所以任务队列的容量不限定
线程池的四个成员变量:
Mutex.hpp 锁的封装
#pragma once
#include
#include
class Mutex
{
public:
Mutex(pthread_mutex_t *lock_p = nullptr): lock_p_(lock_p)
{}
void lock()
{
if(lock_p_) pthread_mutex_lock(lock_p_);
}
void unlock()
{
if(lock_p_) pthread_mutex_unlock(lock_p_);
}
~Mutex()
{}
private:
pthread_mutex_t *lock_p_;
};
class LockGuard
{
public:
LockGuard(pthread_mutex_t *mutex): mutex_(mutex)
{
mutex_.lock(); //在构造函数中进行加锁
}
~LockGuard()
{
mutex_.unlock(); //在析构函数中进行解锁
}
private:
Mutex mutex_;
};
ThreadPool.hpp 主要框架(唤醒和等待操作都已经封装好)
#pragma once
#include "Thread.hpp"
#include "Mutex.hpp"
#include
#include
#include
#include
using namespace wzh;
const int gnum = 5;
template <class T>
class ThreadPool;
template <class T>
class ThreadData
{
public:
ThreadPool<T> *threadpool;
std::string name;
ThreadData(ThreadPool<T> *tp, const std::string &n)
: threadpool(tp), name(n)
{
}
};
template <class T>
class ThreadPool
{
// 单例模式要把构造设置为私有,不能没有!
private:
ThreadPool(const int num = gnum)
: _num(num)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
for (int i = 0; i < _num; ++i)
{
_threads.push_back(new Thread());
}
}
// 赋值重载不能写
void operator=(const ThreadPool &) = delete;
// 拷贝构造也不能写
ThreadPool(const ThreadPool &) = delete;
public:
// 属于线程池的类内成员,需要加static
// 既然是static就不能访问类内的成员方法
static void *handlerTask(void *args)
{
ThreadData<T> *tp = (ThreadData<T> *)args;
CalTask t;
while (true)
{
{
// tp->threadpool->LockQueue();
LockGuard lock(tp->threadpool->mutex());
while (tp->threadpool->IsQueueEmpty())
{
tp->threadpool->ThreadWait();
}
t = tp->threadpool->pop();
// 不能这样写
// 这样就是加锁 拿任务 处理 解锁,这样会导致处理任务是串行的
// t(); //处理任务 bug
// tp->threadpool->UnLockQueue();
}
cout << tp->name << " 处理完了这个任务 " << t.toTaskstring() << " 并处理完成 " << t() << endl;
}
delete tp;
return nullptr;
}
void LockQueue()
{
pthread_mutex_lock(&_mutex);
}
void UnLockQueue()
{
pthread_mutex_unlock(&_mutex);
}
bool IsQueueEmpty()
{
return _task_queue.empty();
}
void ThreadWait()
{
pthread_cond_wait(&_cond, &_mutex);
}
pthread_mutex_t *mutex()
{
return &_mutex;
}
void start()
{
for (const auto &t : _threads)
{
ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
t->start(handlerTask, td);
cout << t->threadname() << " start..." << endl;
}
}
// 向队列push任务
void Push(const T &in)
{
LockGuard lock(&_mutex);
_task_queue.push(in);
// 环形在特定条件变量下等待的线程
pthread_cond_signal(&_cond);
}
T pop()
{
T t = _task_queue.front();
_task_queue.pop();
return t;
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
for (const auto &t : _threads)
{
delete t;
}
}
// 获取单例
// 单例,一个类一个对象,所以设置static
// 懒汉模式
static ThreadPool<T> *getInstance()
{
// tp是静态成员,所有使用该类的线程都能访问到tp,他是临界资源,要加锁
// 提前判断,可以避免上来就竞争锁
if (tp == nullptr)
{
_signlelock.lock();
if (tp == nullptr)
{
tp = new ThreadPool<T>();
}
_signlelock.unlock();
}
return tp;
}
private:
int _num; // 创建线程的个数
vector<Thread *> _threads; // 存放线程
queue<T> _task_queue; // 从队列里拿任务,共享资源,需要加锁保护
pthread_mutex_t _mutex;
pthread_cond_t _cond;
static ThreadPool<T> *tp;
static std::mutex _signlelock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;
template <class T>
std::mutex ThreadPool<T>::_signlelock;
注意:
thread.hpp 封装线程
需要注意的是,创建一个线程还需要提供一个线程启动后要执行的函数,这个启动函数只能有一个参数。如果把这个函数设置为成员函数,那么这个函数的第一个参数默认是this指针,这样显然是不可行的,所以这里我们考虑把这个启动函数设置为静态的。但是设置为静态的成员函数又会面临一个问题:如何调用其他成员函数和成员变量? 所以这里我们考虑创建线程的时候,把this指针传过去,让启动函数的arg 参数去接收即可
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
namespace wzh
{
class Thread
{
private:
static void *start_routine(void *args) // 类内成员,使用static没有this指针了
{
// 把对应的方法再做一下包 装
Thread *ctx = (Thread *)args;
return ctx->callback();
// 静态方法不能调用成员方法,把成员变量变量变为static可以,但不是好方法!
// return func_(args);
}
public:
typedef function<void *(void *)> func_t;
const int num = 1024;
Thread()
{
char namebuffer[num];
snprintf(namebuffer, sizeof(namebuffer), "thread-%d", threadnum++);
name_ = namebuffer;
}
void start(func_t func, void *args = nullptr)
{
func_=func;
args_=args;
int n = pthread_create(&tid_, nullptr, start_routine, this);
assert(n == 0);
(void)n;
}
void *callback()
{
return func_(args_);
}
void join()
{
int n = pthread_join(tid_, nullptr);
assert(n == 0);
(void)n;
}
~Thread()
{
// do nothing
}
std::string threadname()
{
return name_;
}
private:
string name_;
pthread_t tid_;
func_t func_;
void *args_;
static int threadnum;
};
int Thread::threadnum=1;
void *getTicket(void *args)
{
string work_type = (char *)args;
while (true)
{
cout << "我是一个新线程,我正在做:" << work_type << endl;
sleep(1);
}
}
}
**注意:**线程创建后,执行启动函数,在这个函数中,线程会去任务队列中取任务并处理,取任务前需要进行加锁的操作(如果队列为空需要挂起等待),取完任务然后进行解锁,然后处理任务,让其它线程去任务队列中取任务
task.hpp 构造任务
#pragma once
#include
#include
#include
#include
class CalTask
{
using func_t=std::function<int(int,int,char)>;
// typedef std::function func_t;
public:
CalTask()
{}
CalTask(int x,int y,char op,func_t func)
:_x(x)
,_y(y)
,_op(op)
,_func(func)
{}
std::string operator()()
{
int res=_func(_x,_y,_op);
char buffer[64];
snprintf(buffer,sizeof(buffer),"%d %c %d = %d",_x,_op,_y,res);
return buffer;
}
std::string toTaskstring()
{
char buffer[64];
snprintf(buffer,sizeof(buffer),"%d %c %d = ?",_x,_op,_y);
return buffer;
}
private:
int _x;
int _y;
char _op;
func_t _func;
};
class SaveTask
{
using func_t=std::function<void(const std::string&)>;
public:
SaveTask()
{}
SaveTask(std::string& message,func_t func)
:_message(message)
,_func(func)
{}
void operator()()
{
_func(_message);
}
private:
std::string _message;
func_t _func;
};
void save(const std::string& message)
{
const std::string target="log.txt";
FILE* fp=fopen(target.c_str(),"a+");
if(fp==nullptr)
{
perror("fopen");
return ;
}
fputs(message.c_str(),fp);
fputs("\n",fp);
fclose(fp);
}
const std::string oper = "+-*/%";
int mymath(int x, int y, char op)
{
int result = 0;
switch (op)
{
case '+':
result = x + y;
break;
case '-':
result = x - y;
break;
case '*':
result = x * y;
break;
case '/':
{
if (y == 0)
{
std::cerr << "div zero error!" << std::endl;
result = -1;
}
else
result = x / y;
}
break;
case '%':
{
if (y == 0)
{
std::cerr << "mod zero error!" << std::endl;
result = -1;
}
else
result = x % y;
}
break;
default:
// do nothing
break;
}
return result;
}
test.cpp 主线程
主线程负责创建线程池,然后塞任务即可
#include "Thread.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include
#include
int main()
{
// std::unique_ptr> tp(new ThreadPool());
ThreadPool<CalTask>::getInstance()->start();
// tp->start();
while(1)
{
int x,y;
char op;
cout << "请输入数据1" << endl;
cin >> x ;
cout << "请输入数据2" << endl;
cin >> y;
cout << "请输入运算符" << endl;
cin >> op;
CalTask t(x,y,op,mymath);
ThreadPool<CalTask>::getInstance()->Push(t);
}
return 0;
}