スレッドプールとシングルトンモード
1. スレッドプールとは何ですか
スレッド プール: スレッドの使用パターン。スレッドが多すぎると、スケジューリングのオーバーヘッドが発生し、キャッシュの局所性と全体的なパフォーマンスに影響します。スレッド プールは複数のスレッドを維持し、スーパーバイザが同時に実行できるタスクを割り当てるのを待ちます。これにより、存続期間の短いタスクを処理するときにスレッドを作成および破棄するコストが回避されます。スレッド プールは、コアの完全な利用を保証するだけでなく、過剰なスケジューリングを防止することもできます。使用可能なスレッドの数は、使用可能な同時プロセッサ、プロセッサ コア、メモリ、ネットワーク ソケットなどの数によって異なります。
スレッド プールのアプリケーション シナリオ:
- タスクを完了するには多数のスレッドが必要ですが、タスクの完了にかかる時間は比較的短くなります。
- サーバーがクライアント要求に迅速に応答する必要があるなど、パフォーマンスが重要なアプリケーション。
- 突然の大量のリクエストを受け入れるが、サーバーに大量のスレッドを生成させることのないアプリケーション。
2. デザインアイデア
3. コードの実装
注:私の個人的な不注意により将ThreadPool写成了ThredaPool
、
//Thread.hpp
#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <pthread.h>
#include <cassert>
namespace ThreadCat
{
typedef std::function<void *(void *)> func_t;
const int num = 1024;
class Thread
{
private:
// 使用静态方法
static void *start_rountine(void *args)
{
Thread *this_ = static_cast<Thread *>(args);
return this_->callback();
}
public:
Thread()
{
char namebuffer[num];
snprintf(namebuffer, sizeof namebuffer, "thread-%d", thread_num++);
_name=namebuffer;
}
void start(func_t func, void *args=nullptr)
{
_func=func;
_args=args;
int n = pthread_create(&_tid, nullptr, start_rountine, this);
assert(n == 0);
(void)n;
}
void join()
{
int n = pthread_join(_tid, nullptr);
assert(n == 0);
(void)n;
}
std::string threadname()
{
return _name;
}
void *callback()
{
return _func(_args);
}
~Thread(){
}
private:
std::string _name;
pthread_t _tid;
func_t _func;
void *_args;
static int thread_num;
};
int Thread::thread_num=1;
}
//Task.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cstring>
#include <functional>
class Task//计算任务
{
public:
using func_t =std::function<int(int,int,char)>;
Task(){
}
Task(int x,int y,char op,func_t callback)
:x_(x),y_(y),op_(op),callback_(callback)
{
}
std::string operator()()
{
int result=callback_(x_,y_,op_);
char buffer[1024];
snprintf(buffer,sizeof buffer,"%d %c %d=%d",x_,op_,y_,result);
return buffer;
}
std::string toTaskString()
{
char buffer[1024];
snprintf(buffer,sizeof buffer,"%d %c %d=?",x_,op_,y_);
return buffer;
}
private:
int x_;
int y_;
char op_;
func_t callback_;
};
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:
break;
}
return result;
}
//lockguard.hpp
#pragma once
#include <iostream>
#include <pthread.h>
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_;
};
//RAII思想管理资源
class LockGuard
{
public:
LockGuard(pthread_mutex_t* lock_p)//用传过来的锁去初始化Mutex
:mutex(lock_p)
{
mutex.lock();
}
~LockGuard()
{
mutex.unlock();
}
private:
Mutex mutex;
};
//ThreadPool.hpp
#pragma once
#include "Thread.hpp"
#include "lock_guard.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
using namespace ThreadCat;
const int gnum = 5;
template <class T>
class ThredaPool;
template <class T>
class ThreadData
{
public:
ThreadData(ThredaPool<T> *tp, const std::string &n)
: threadpool(tp), name(n)
{
}
public:
ThredaPool<T> *threadpool;
std::string name;
};
template <class T>
class ThredaPool
{
private:
static void *handlerTask(void *args)
{
ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
while (true)
{
T t;
{
LockGuard lock(td->threadpool->mutex());
//td->threadpool->lockQueue();
while (td->threadpool->isQueueEmpty())
{
td->threadpool->threadWait();
}
// pop本质,就是把任务从公共队列中拿到当前线程独立的栈中
t = td->threadpool->pop();
//td->threadpool->unlockQueue();
}
std::cout << td->name << "获取了一个" << t.toTaskString() << "并处理完成,结果是" << t() << std::endl;
}
delete td;
return nullptr;
}
public:
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_); }
T pop()
{
T t = task_queue_.front();
task_queue_.pop();
return t;
}
pthread_mutex_t* mutex()
{
return &mutex_;
}
public:
ThredaPool(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 run()
{
for (const auto &t : threads_)
{
ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
t->start(handlerTask, td);
std::cout << t->threadname() << std::endl;
}
}
void push(const T &in)
{
LockGuard lock(&mutex_);
//pthread_mutex_lock(&mutex_);
task_queue_.push(in);
pthread_cond_signal(&cond_);
//pthread_mutex_unlock(&mutex_);
}
~ThredaPool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
for (const auto &t : threads_)
{
delete t;
}
}
private:
int num_;
std::vector<Thread *> threads_;
std::queue<T> task_queue_;
pthread_mutex_t mutex_;
pthread_cond_t cond_;
};
//main.cc
#include "ThreadPool.hpp"
#include "Thread.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <memory>
using std::cout;
using std::endl;
int main()
{
std::unique_ptr<ThredaPool<Task>> tp(new ThredaPool<Task>());
tp->run();
srand((size_t)time(0) ^ 0x22116652);
int x, y;
char op;
while (true)
{
x = rand() % 10 + 1;
y = rand() % 5 + 1;
op = oper[rand() % oper.size()];
Task t(x, y, op, mymath);
tp->push(t);
sleep(1);
}
return 0;
}
上記のコードでは、まずスレッド ライブラリ Thread、タスク クラス Task、および RAII スタイルのロックをカプセル化してから、ThreadPool ファイルを書き込みます。main.cc で開始すると、まずスレッド プール オブジェクトを作成し、その run メソッドを呼び出します。run メソッドは、スレッド ライブラリ内の start メソッドを呼び出し、スレッドによって実行される関数 (handlerTask) とパラメーターを渡します。関数 ( ThreadData *) に供給され、Thread ライブラリでは、start メソッドは pthread_create() を使用してスレッドを作成し、関数に戻ります (実際、ここでの丸は、呼び出し時のこのポインタの問題を解決するためのものです)クラス内のメソッドを使用するには、Set メソッドを static に設定する必要がありますが、静的メソッドは非静的メンバーを使用できず、ThreadPool オブジェクトを含む大きな構造体 (ThreadData) が必要です。次に、メイン スレッドは継続的にタスクをタスク キューにプッシュし、スレッド プール内のスレッドは handlerTask 関数を実行してタスクがあるかどうかを確認します (タスクのブロック待機はありません)。タスクがある場合は、スレッド プールから独自の独立したスタックを取得します。実行するパブリックキュー。
4. スレッドプールに基づくシングルトンモード
シングルトン パターンは、クラスにインスタンスが 1 つだけ存在することを保証し、このインスタンスにアクセスするためのグローバル アクセス ポイントを提供する作成設計パターンです。このモードは、データベース接続やスレッド プールなどのリソースへのアクセスを制御するためによく使用されます。シングルトン パターンの中心的な考え方は、クラスのインスタンス化プロセスを単一のオブジェクトに制限し、そのオブジェクトを取得するためのグローバル アクセス ポイントを提供することです。このアクセス ポイントは通常、プログラム内のどこからでも呼び出すことができる静的メソッドです。
シングルトン パターンの利点は次のとおりです。
-
クラスにインスタンスが 1 つだけあるようにすると、システム リソースが節約されます。
-
プログラムの呼び出しと管理を容易にするために、グローバル アクセス ポイントが提供されます。
-
オブジェクトの一貫性が保証され、複数のインスタンスによって引き起こされるデータの不整合の問題が回避されます。
-
インスタンス化プロセスを制御して、オブジェクトの安全性と正確性を確保できます。
4.1 怠け者モードのデザインアイデア
- コンストラクターをプライベート化し、コピー構築と代入演算子のオーバーロードを禁止する
- スレッド プールに基づいてメンバー変数を追加します: 静的ミューテックス、静的スレッド プール ポインター ()
- スレッド プールに基づいて静的メソッドを追加します。一意のオブジェクトを取得します。
#include <mutex>
const int gnum = 5;
template <class T>
class ThreadPool;
template <class T>
class ThreadData
{
public:
ThreadData(ThreadPool<T> *tp, const std::string &n)
: threadpool(tp), name(n)
{
}
public:
ThreadPool<T> *threadpool;
std::string name;
};
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());
}
}
ThreadPool &operator=(const ThreadPool &) = delete;
ThreadPool(const ThreadPool &) = delete;
static void *handlerTask(void *args)
{
ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
while (true)
{
T t;
{
LockGuard lock(td->threadpool->mutex());
// td->threadpool->lockQueue();
while (td->threadpool->isQueueEmpty())
{
td->threadpool->threadWait();
}
// pop本质,就是把任务从公共队列中拿到当前线程独立的栈中
t = td->threadpool->pop();
// td->threadpool->unlockQueue();
}
std::cout << td->name << "获取了一个" << t.toTaskString() << "并处理完成,结果是" << t() << std::endl;
}
delete td;
return nullptr;
}
public:
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_); }
T pop()
{
T t = task_queue_.front();
task_queue_.pop();
return t;
}
pthread_mutex_t *mutex()
{
return &mutex_;
}
public:
void run()
{
for (const auto &t : threads_)
{
ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
t->start(handlerTask, td);
std::cout << t->threadname() << std::endl;
}
}
void push(const T &in)
{
LockGuard lock(&mutex_);
// pthread_mutex_lock(&mutex_);
task_queue_.push(in);
pthread_cond_signal(&cond_);
// pthread_mutex_unlock(&mutex_);
}
~ThreadPool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
for (const auto &t : threads_)
{
delete t;
}
}
static ThreadPool<T> *GetInstance()
{
if (tp == nullptr)
{
//std::lock_guard<mutex> lock(siglock);
siglock.lock();
if (nullptr == tp)
{
tp = new ThreadPool<T>();
}
siglock.unlock();
}
return tp;
}
private:
int num_;
std::vector<Thread *> threads_;
std::queue<T> task_queue_;
pthread_mutex_t mutex_;
pthread_cond_t cond_;
volatile static ThreadPool<T> *tp;
static std::mutex siglock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;
template <class T>
std::mutex ThreadPool<T>::siglock;
//main.cc
ThreadPool<Task>::GetInstance()->run();
5. リーダーライター問題と読み書きロック
マルチスレッドを記述する場合、非常に一般的な状況があります。つまり、一部の公開データは変更される可能性が低くなります。リライトに比べて、読める可能性ははるかに高くなります。一般に、読書の過程では検索という操作を伴うことが多く、途中で時間がかかってしまいます。この種のコード セグメントをロックすると、プログラムの効率が大幅に低下します。
321 原則は、リーダーとライターの問題にも当てはまります。
3 つの関係: リーダーとライターは相互に排他的であり、同期されています。リーダーとリーダーには関係がありません。ライターとライターは相互に排他的です。 2 つの役割: リーダーとライター 1 つの
取引
場所
リーダー/ライター問題と生産者/消費者モデルの本質的な違いは、消費者はデータを取得しますが、リーダーはデータを取得しないことです。
読み取り/書き込みロックの動作
現在のロック状態 | 読み取りロック要求 | 書き込みロック要求 |
---|---|---|
ロックなし | できる | できる |
読み取りロック | できる | ブロック |
書き込みロック | ブロック | ブロック |
//初始化读写锁
pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
//销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//读者加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//写者加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//解除锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
読んでいただきありがとうございます。Linux システム プログラミングはこれで終わりです。その後、ネットワーク プログラミングと C++ に関するブログをゆっくり更新していきます。