【ネットワークプログラミング】半同期--半非同期スレッドプールソース分析のタスクキュー(C++11ベース)
10340 ワード
前言
C++の勉强に対して、本を読むだけでは、勉强の能率が低いような気がします.多くの新しい知識と新しい概念の理解は難しいが、C++11はより多くの新しい概念と知識を導入している.サービス側を学ぶ部分では、「同期–非同期」も人を酔わせる.
本が読めず、概念が読めないなら、例を探して分析したほうがいいです.少なくとも早く手に入れることができます.
陳さんが私たちに『オペレーティングシステム』を書いてくれたときに言った理論と実例化したものを結びつけなければならないことを思い出した.私のような不器用な子供には特に適しているが、理論だけは本当にToTを読めない.
本文は主に1つの例(半同期–半非同期スレッドプール)のソースコード分析を通じて、サービス端における同時モードを体得すると同時に、コードはほとんどC++11を使用して開発されているため、C++11の新しい概念についても説明する.したがって,本稿はC++11/サービスエンド開発を学習したいパートナーに適している.この例は『C++11-コード最適化とエンジニアリングレベル応用を深く応用する』の第9章から出ている.ソースコードはすでに著者のgithubにオープンソースされている.げんコードアドレス
本文
同期キューテンプレートクラスSyncQueue
まず、プライベートメンバー変数を見てみましょう.
コンストラクタ
まず、最も簡単な3つのpublicメソッドを見てみましょう.主に反発量std::mutexを使用してキューへのアクセスを保証します.
std::mutexの一般的な使い方は以下の通りです.
このように直接ロックをかけてロックを解除するのは直感的ですが、手動でunlockする必要があります.unlockを忘れると気まずいことがあります.簡単なstd::lock_を使いますguard locker(m_mutex)というlockerは構造時にロックをかけ,構造時にロックを解除するので,この3つの関数が互いに反発して臨界領域にアクセスすることを保証できる.
2つの主要な操作Take&Put
実はTakeとPutの流れは基本的に同じで、主に条件変数conditionを使っています.variableはスレッドの実行順序を制御し、キューが空の場合Putスレッドが実行され、キューが満の場合Takeスレッドが実行されることを保証する.条件変数の使用にはロックが必要であり、std::unique_でなければならない.lock
条件変数の使用方法は
すなわち、まずスレッド競合でロックロックロックを取得し、std::unique_を使用する必要があることに注意する.ロックはwaitメソッドを呼び出す.ここでwaitメソッドのリロードバージョンを用いる、まず述語がtrueであるか否かを判断し、直接継続する.そうでなければ、スレッドがブロックされ、waitはlockerをロック解除し(他のスレッドがロックを取得できるように)、他のスレッドがm_を通過するのを待つcvのnotify_*方法はそれを呼び覚ます.一旦起動すると、鍵で述語を判断し続ける.waitには述語を必要とせず、ロック解除を直接ブロックし、notify_*を待つバージョンもあります.方法起動、起動後にロックを持って実行を継続する.waitメソッドには、自動的にロックを解除するプロセスがあるため、std::unique_を使用する必要があります.lock .
操作の終了
キューが空/満であるかどうかを判断
C++の勉强に対して、本を読むだけでは、勉强の能率が低いような気がします.多くの新しい知識と新しい概念の理解は難しいが、C++11はより多くの新しい概念と知識を導入している.サービス側を学ぶ部分では、「同期–非同期」も人を酔わせる.
本が読めず、概念が読めないなら、例を探して分析したほうがいいです.少なくとも早く手に入れることができます.
陳さんが私たちに『オペレーティングシステム』を書いてくれたときに言った理論と実例化したものを結びつけなければならないことを思い出した.私のような不器用な子供には特に適しているが、理論だけは本当にToTを読めない.
本文は主に1つの例(半同期–半非同期スレッドプール)のソースコード分析を通じて、サービス端における同時モードを体得すると同時に、コードはほとんどC++11を使用して開発されているため、C++11の新しい概念についても説明する.したがって,本稿はC++11/サービスエンド開発を学習したいパートナーに適している.この例は『C++11-コード最適化とエンジニアリングレベル応用を深く応用する』の第9章から出ている.ソースコードはすでに著者のgithubにオープンソースされている.げんコードアドレス
本文
同期キューテンプレートクラスSyncQueue
まず、プライベートメンバー変数を見てみましょう.
private:
std::list m_queue; // std::list
std::mutex m_mutex; //C++11
std::condition_variable m_notEmpty; //C++11 ,
std::condition_variable m_notFull; //
int m_maxsize; //
bool m_needStop; // , false
};
コンストラクタ
// : , , m_needStop false
SyncQueue(int maxsize) : m_maxsize(maxsize), m_needStop(false) {}
まず、最も簡単な3つのpublicメソッドを見てみましょう.主に反発量std::mutexを使用してキューへのアクセスを保証します.
// public (size ),
// std::lock_guard()
bool Empty() //
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full() //
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size() == m_maxsize;
}
size_t Size() //
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
std::mutexの一般的な使い方は以下の通りです.
#include
std::mutex m;
void fun(){
m.lock();
//
m.unlock();
}
このように直接ロックをかけてロックを解除するのは直感的ですが、手動でunlockする必要があります.unlockを忘れると気まずいことがあります.簡単なstd::lock_を使いますguard locker(m_mutex)というlockerは構造時にロックをかけ,構造時にロックを解除するので,この3つの関数が互いに反発して臨界領域にアクセスすることを保証できる.
2つの主要な操作Take&Put
実はTakeとPutの流れは基本的に同じで、主に条件変数conditionを使っています.variableはスレッドの実行順序を制御し、キューが空の場合Putスレッドが実行され、キューが満の場合Takeスレッドが実行されることを保証する.条件変数の使用にはロックが必要であり、std::unique_でなければならない.lock
// , Take
// std::list ( list )
//
void Take(std::list & list)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this]{
return m_needStop || NotEmpty(); }); // lambda this private
// ,
// m_needStop true
if(m_needStop)
return;
list = std::move(m_queue);// move ,
m_notFull.notify_one(); // Put
}
// Take ,
void Take(T& x)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this]{
return m_needStop || NotEmpty();});
if(m_needStop)
return ;
x = m_queue.front();
m_queue.pop_front(); //
m_notFull.notify_one();
}
条件変数の使用方法は
std::mutex m_mutex;
std::condition_varible m_cv;
void func(){
std::unique_lock::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this]{
return isTrue();});
//
m_cv.notify_one();
}
すなわち、まずスレッド競合でロックロックロックを取得し、std::unique_を使用する必要があることに注意する.ロックはwaitメソッドを呼び出す.ここでwaitメソッドのリロードバージョンを用いる、まず述語がtrueであるか否かを判断し、直接継続する.そうでなければ、スレッドがブロックされ、waitはlockerをロック解除し(他のスレッドがロックを取得できるように)、他のスレッドがm_を通過するのを待つcvのnotify_*方法はそれを呼び覚ます.一旦起動すると、鍵で述語を判断し続ける.waitには述語を必要とせず、ロック解除を直接ブロックし、notify_*を待つバージョンもあります.方法起動、起動後にロックを持って実行を継続する.waitメソッドには、自動的にロックを解除するプロセスがあるため、std::unique_を使用する必要があります.lock .
// , Add
// Put Add , ( , , )
void Put(const T &x)//
{
Add(x); //
}
void Put(T &&x) //
{
Add(std::forward(x)); // ,
}
private:
template <typename F>
void Add(F &&x)
{
// ,
std::unique_lock<std::mutex> locker(m_mutex);
m_notFull.wait(locker, [this]{
return m_needStop || NotFull();});
if(m_needStop)
return;
m_queue.push_back(std::forward(x));//
m_notEmpty.notify_one();
}
操作の終了
//
void Stop()
{
// ,
{
std::lock_guard<std::mutex> locker(m_mutex);
m_needStop = true;//
}
// ( ,locker , )
// , .
m_notFull.notify_all(); // Take Put , return
m_notEmpty.notify_all();
}
キューが空/満であるかどうかを判断
private:
// ,
bool NotFull() const // const
{
bool full = m_queue.size() >= m_maxsize;
if(full)
std::cout << "buffer is full ...please wait" << std::endl;
else
return !full;
}
bool NotEmpty() const
{
bool empty = m_queue.empty();
if(empty){
std::cout << "buffer is empty... please wait. Thread ID:" << std::this_thread::get_id() << std::endl;
}
return !empty;
}