C++ プログラムを開発する場合、一般にスループット、同時実行性、リアルタイム パフォーマンスの点でより高い要件が求められます。 C++ プログラムを設計する場合、要約すると、次の点から効率を向上させることができます:
ここでは、私が日常的に遭遇するいくつかの問題の例をいくつか示します。作品の設計思想は上記の3点にほかなりません。
1 タスクキュー
1.1 プロデューサー-コンシューマーモデルに基づいてタスクキューを設計する
プロデューサー-コンシューマーモデルは、たとえばサーバープログラムでユーザーデータが変更されるときによく知られているモデルです。ロジックモジュールにより、データベースを更新するタスクが生成(プロデュース)され、IOモジュールのタスクキューに渡されます。IOモジュールはタスクキューからタスクを取り出し、SQL操作を実行します(コンシューム)。
一般的なタスク キューを設計します。サンプル コードは次のとおりです:
詳細な実装は次の場所にあります:
http://ffown.googlecode.com/svn/trunk/fflib/include/detail/task_queue_impl.h
void task_queue_t::produce(const task_t& task_) {
lock_guard_t lock(m_mutex);
if (m_tasklist->empty()){
//! 条件满足唤醒等待线程
m_cond.signal();
}
m_tasklist->push_back(task_);
}
int task_queue_t::comsume(task_t& task_){
lock_guard_t lock(m_mutex);
while (m_tasklist->empty())
//! 当没有作业时,就等待直到条件满足被唤醒{
if (false == m_flag){
return -1;
}
m_cond.wait();
}
task_ = m_tasklist->front();
m_tasklist->pop_front();
return 0;
}
1.2 タスクキューを使用するためのヒント
1.2.1 IOとロジックの分離
たとえば、オンラインゲームサーバープログラムでは、ネットワークモジュールはメッセージパケットを受信し、それをロジック層に配信した後すぐに戻ります、次のメッセージ パケットの受け入れを続けます。論理スレッドは、リアルタイムのパフォーマンスを確保するために、IO 操作のない環境で実行されます。例:
void handle_xx_msg(long uid, const xx_msg_t& msg){
logic_task_queue->post(boost::bind(&servie_t::proces, uid, msg));
}
このモードはシングル タスク キューであり、各タスク キューはシングル スレッドであることに注意してください。
1.2.2 並列パイプライン
上記は、IO および CPU 操作の並列化のみを完了しますが、CPU での論理操作はシリアルです。場合によっては、CPU ロジック演算部分も並列化できます。たとえば、ゲームでは、ユーザー A とユーザー B の 2 つの操作はデータを共有しないため、完全に並列化できます。最も簡単な方法は、A と B に関連する操作を別のタスク キューに割り当てることです。例は次のとおりです。
void handle_xx_msg(long uid, const xx_msg_t& msg) {
logic_task_queue_array[uid % sizeof(logic_task_queue_array)]->post(
boost::bind(&servie_t::proces, uid, msg));
}
このモードはマルチタスク キューであり、各タスク キューはシングルスレッドであることに注意してください。
1.2.3 接続プールと非同期コールバック
たとえば、論理サービス モジュールでは、データベース モジュールがユーザー データを非同期でロードし、後続の処理と計算を実行する必要があります。データベース モジュールには、固定数の接続を持つ接続プールがあり、SQL を実行するタスクが到着すると、アイドル状態の接続を選択し、SQL を実行し、コールバック関数を通じて SQL をロジック層に渡します。手順は次のとおりです。
スレッド プールを事前に割り当て、各スレッドがデータベースへの接続を作成します
データベース モジュールのタスク キューを作成し、すべてのスレッドがこのタスク キューのコンシューマになります
ロジック層データベースモジュールを考慮します SQL 実行タスクを配信し、SQL 実行結果を受け取るコールバック関数を渡します
例は次のとおりです:
void db_t:load(long uid_, boost::functionpost(boost::bind(&db_t:load, uid, func)); このモードは単一のタスクキューであり、各タスクキューはマルチスレッドであることに注意してください。
2. ログ
この記事では主に C++ マルチスレッド プログラミングについて説明します。ログ システムはプログラムの効率を向上させるためのものではありませんが、プログラムのデバッグや実行時のトラブルシューティングにとって、ログは開発においてかけがえのないツールであると私は信じています。友人がログを使用するバックグラウンド プログラム。一般的なログの使用方法には次のようなものがあります:
ストリーミング (logstream << "start service time[%d]" <
Printf 形式: logtrace(LOG_MODULE, "サービス開始時刻[%d] アプリ名[%s]", time(0), app_string . c_str());
ストリーミングはスレッドセーフであり、printf 形式は文字列のフォーマットに直接的ですが、app_string.c_str の場合はスレッドセーフでないという欠点があります。 () app_string (std::string) に変更すると、コンパイルは成功しますが、実行時にクラッシュします (運が良ければ毎回クラッシュしますが、運が悪いと時々クラッシュします)。私は個人的に printf スタイルが大好きなので、次のような改善が可能です:
スレッドの安全性を高め、C++ テンプレートの特性メカニズムを使用してスレッドの安全性を実現します。例:
template
void logtrace(const char* module, const char* fmt, ARG1 arg1){
boost::format s(fmt);
f % arg1;
} このように、標準型 + std::string に加えて、渡された他の型はコンパイルに失敗します。これは 1 つのパラメーターの単なる例であり、必要に応じて、このバージョンをオーバーロードして、さらに多くのパラメーター (9 パラメーター以上) をサポートすることができます。
ログに色を追加し、printf に制御文字を追加すると、その色が画面端末に表示されます。 Linux での例: printf("33[32;49;1m [DONE] 33[39;49;0m"] )
その他の配色については、次を参照してください:
http://hi.baidu.com/jiemnij/blog/item/d95df8c28ac2815cb219a80e.html
各スレッドが開始されるとき、スレッドがどのような機能であるかを出力するためにログを使用する必要があります。の責任者。このようにして、プログラムの実行中に、top-H-p pid を通じてその関数によってどのくらいの CPU が使用されているかを知ることができます。実際、ログの各行にはスレッド ID が出力されます。このスレッド ID は pthread_id ではなく、実際にはスレッドに対応するシステムによって割り当てられたプロセス ID 番号です。
3. パフォーマンス監視
C++ プログラムの実行パフォーマンスを分析できるツールは数多くありますが、そのほとんどは依然としてプログラムのデバッグ段階で実行されます。デバッグ フェーズとリリース フェーズの両方でプログラムを監視する方法が必要ですが、一方ではプログラムのボトルネックがどこにあるのかを知ることができ、他方ではどのコンポーネントが異常であるかをできるだけ早く見つけることができます。実行時。
通常都是使用gettimeofday 来计算某个函数开销,可以精确到微妙。可以利用C++的确定性析构,非常方便的实现获取函数开销的小工具,示例如下:
struct profiler{
profiler(const char* func_name){
gettimeofday(&tv, NULL);
}
~profiler(){
struct timeval tv2;
gettimeofday(&tv2, NULL);
long cost = (tv.tv_sec - tv.tv_sec) * 1000000 + (tv.tv_usec - tv.tv_usec);
//! post to some manager
}
struct timeval tv;
};
#define PROFILER() profiler(__FUNCTION__) Cost 应该被投递到性能统计管理器中,该管理器定时讲性能统计数据输出到文件中。
4 Lambda 编程
使用foreach 代替迭代器
很多编程语言已经内建了foreach,但是c++还没有。所以建议自己在需要遍历容器的地方编写foreach函数。习惯函数式编程的人应该会非常钟情使用foreach,使用foreach的好处多多少少有些,如:
http://www.cnblogs.com/chsword/archive/2007/09/28/910011.html
但主要是编程哲学上层面的。
示例:
void user_mgr_t::foreach(boost::function func_){
for (iterator it = m_users.begin(); it != m_users.end() ++it){
func_(it->second);
}
}
比如要实现dump 接口,不需要重写关于迭代器的代码
void user_mgr_t:dump(){
struct lambda {
static void print(user_t& user){
//! print(tostring(user);
}
};
this->foreach(lambda::print);
} 实际上,上面的代码变通的生成了匿名函数,如果是c++ 11 标准的编译器,本可以写的更简洁一些:
this->foreach([](user_t& user) {} ); 但是我大部分时间编写的程序都要运行在centos 上,你知道吗它的gcc版本是gcc 4.1.2, 所以大部分时间我都是用变通的方式使用lambda函数。
Lambda 函数结合任务队列实现异步
常见的使用任务队列实现异步的代码如下:
void service_t:async_update_user(long uid){
task_queue->post(boost::bind(&service_t:sync_update_user_impl, this, uid));
}
void service_t:sync_update_user_impl(long uid){
user_t& user = get_user(uid);
user.update()
}
这样做的缺点是,一个接口要响应的写两遍函数,如果一个函数的参数变了,那么另一个参数也要跟着改动。并且代码也不是很美观。使用lambda可以让异步看起来更直观,仿佛就是在接口函数中立刻完成一样。示例代码:
void service_t:async_update_user(long uid){
struct lambda {
static void update_user_impl(service_t* servie, long uid){
user_t& user = servie->get_user(uid);
user.update();
}
};
task_queue->post(boost::bind(&lambda:update_user_impl, this, uid));
} 这样当要改动该接口时,直接在该接口内修改代码,非常直观。
5. 奇技淫巧
利用 shared_ptr 实现 map/reduce
Map/reduce的语义是先将任务划分为多个任务,投递到多个worker中并发执行,其产生的结果经reduce汇总后生成最终的结果。Shared_ptr的语义是什么呢?当最后一个shared_ptr析构时,将会调用托管对象的析构函数。语义和map/reduce过程非常相近。我们只需自己实现讲请求划分多个任务即可。示例过程如下:
定义请求托管对象,加入我们需要在10个文件中搜索“oh nice”字符串出现的次数,定义托管结构体如下:
struct reducer{
void set_result(int index, long result) {
m_result[index] = result;
}
~reducer(){
long total = 0;
for (int i = 0; i < sizeof(m_result); ++i){
total += m_result[i];
}
//! post total to somewhere
}
long m_result[10];
};
定义执行任务的 worker
void worker_t:exe(int index_, shared_ptr ret) {
ret->set_result(index, 100);
}
将任务分割后,投递给不同的worker
shared_ptr ret(new reducer());
for (int i = 0; i < 10; ++i) { task_queue[i]->post(boost::bind(&worker_t:exe, i, ret));
} 以上就是C++ 多线程编程总结的内容,更多相关内容请关注PHP中文网(www.php.cn)!