自転車を恐れないでください。 または、C ++ 11の別のGrand Central Dispatch(GCD)

私見(私は意見西洋わさびコンテストを持っています)



私の観点からすると、プログラマーがプロのレベルを向上させるためにできる最も便利なことは、バイクを書くことです。 サイクリングは非常に刺激的なプロセスです。 自転車自体が開始したタスク以上のものを運ぶこともあります。 自転車を書くとき(自転車で、私は既存のものを実装することを意味します)、既存のソリューションと技術のより深い理解が生じます。





やる気



3年以上にわたり、私の主な作業言語はObjective-Cでした。最初に記述を始めたとき、マルチスレッドNSOperationQueueを操作するための考え抜かれた高レベルAPIに驚きました。並行性。 そして、ここにHabréに関する最近の記事があります: C ++からのawait / asyncの類似物を書くテクニックとC ++およびスレッドの並行性C ++ 11 。 彼らは、C ++がマルチスレッドで作業するために提供するこれらの新しい利点を強制的に検討しました。 そして、それらのほとんど(同じstd :: future)は、私にとっては次のようなものです。





憶測とウィッシュリスト



以下は、アプリケーションでマルチスレッドを使用する典型的なシナリオです。



便利なことに、これらの操作のそれぞれに独自の順番があります。

さらに、これらすべてが1つの場所に収集され、5つのソースファイルに分散されていない場合はさらに便利です。 次のようなもの:

file_io_queue.async([=]{ file_data = get_data_from_file( file_name ); parser_queue.async([=]{ parsed_data = parse( file_data ); main_queue.async([=]{ update_ui_with_new_data( parsed_data ) ; }); }); });
      
      



このコードは、完全に線形の同期コードのように見えます。 データ変更がどのように発生するかのロジックを説明します。 私にとっては、概して、どのスレッドでファイルが読み込まれ、どのスレッドで解析されるかは重要ではありません。 主なものは、これらの操作のシーケンスです。 100500ファイルについては、以前のコードを100500回呼び出すことができます。



明らかな解決策は、プルストリームテンプレートを実装することです。 しかし、インターネット業界のオープンスペースで見たほとんどすべての実装は、1つのキューに1つのstd ::スレッドを使用することを推奨しています。 私の観点から、これは良くありません。 たとえば、非同期操作が実行されている間、キュー自体のインスタンスを常に保存する必要があります。 std ::スレッドスタンスの作成は、mutexのキャプチャ/リリースよりも桁違いに高価な操作です。 キューをいつ破棄する必要がありますか? はい、そしてキューが使用されていないときのアイドル状態の多数のスレッド-氷ではありません。

異なる方法で行います。 N番目のスレッド(std :: thread)と、優先度のある軽量キューのリストがあります。 キューにタスクを追加すると、新しいタスクが発生したことをスレッドに通知します。 スレッドは、最も優先度の高いタスクを取得して実行します。 この優先度のタスクが既に実行されている場合、優先度の低いタスクを使用します。 存在しない場合、待機しています。





コード



続行:
キュー
 namespace dispatch{ typedef std::function<void ()> function; struct queue { typedef long priority; //  .      const queue::priority queue_priority; static std::shared_ptr<queue> main_queue() ; //    virtual void async(function) const; //        queue(queue::priority priority) : queue_priority(priority) {}; }; }
      
      





非同期メソッドの実装
呼び出しをスレッドプールにリダイレクトするだけです。
  void queue::async(dispatch::function task) const { thread_pool::shared_pool()->push_task_with_priority(task, this->queue_priority); };
      
      





すべての作業は、
スレッドプール:
  struct queue_impl{ const queue::priority priority; std::queue<function> tasks; bool is_running; queue_impl(queue::priority priority): priority(priority){}; }; struct thread_pool{ thread_pool(); static std::shared_ptr<thread_pool>& shared_pool(); // thread_pool virtual ~thread_pool(); bool stop; typedef std::shared_ptr<queue_impl> queue_ptr; void push_task_with_priority(const function&, queue::priority);//       bool get_free_queue(queue_ptr*) const; //  ,        void start_task_in_queue(const queue_ptr&); //     void stop_task_in_queue(const queue_ptr&); //   std::mutex mutex; //     std::map<queue::priority, queue_ptr> queues; //      std::mutex main_thread_mutex; std::queue<dispatch::function> main_queue; std::condition_variable condition; std::vector<std::thread> threads; //   ,      dispatch::function main_loop_need_update; void add_worker(); //     };
      
      





メソッドを順番に検討してください。 最も優先度の高い空きキューを見つける必要があります。
最も優先度の高いフリーキューを見つけます。
  bool thread_pool::get_free_queue(queue_ptr* out_queue) const { //           auto finded = std::find_if(queues.rbegin(), queues.rend(), [](const std::pair<queue::priority, queue_ptr>& iter){ return ! iter.second->is_running; //      }); bool is_free_queue_exist = (finded != queues.rend()); if (is_free_queue_exist) *out_queue = finded->second; return is_free_queue_exist; }
      
      





タスクをキューに追加します
  void thread_pool::push_task_with_priority(const function& task, queue::priority priority){ { std::unique_lock<std::mutex> lock(mutex); //   //    .    -   auto queue = queues[priority]; if (!queue){ queue = std::make_shared<dispatch::queue_impl>(priority); queues[priority] = queue; } queue->tasks.push(task); //  ,    unsigned max_number_of_threads = std::max<unsigned>(std::thread::hardware_concurrency(), 2); unsigned number_of_threads_required = round(log(queues.size()) + 1); while (threads.size() < std::min<unsigned>(max_number_of_threads, number_of_threads_required)){ add_worker(); } } condition.notify_one(); //  ,       }
      
      





タスクを完了済みとしてマークする
  void thread_pool::stop_task_in_queue(const queue_ptr& queue){ { std::unique_lock<std::mutex> lock(mutex); //    .    -      queue->is_running = false; if ( queue->tasks.size() ==0 ){ queues.erase(queues.find(queue->queue_priority)); } } condition.notify_one(); //  ,      }
      
      





そして、実際には、ストリーム自体:
  void thread_pool::add_worker(){ threads.push_back(std::thread([=]{ dispatch::function task; thread_pool::queue_ptr queue; while(true){ { std::unique_lock<std::mutex> lock(mutex); //    while(!stop && !get_free_queue(&queue)) //     condition.wait(lock); //    if(stop) //     ,   return; task = queue->tasks.front(); //     queue->tasks.pop(); start_task_in_queue(queue); //     } task(); //   stop_task_in_queue(queue); //     } })); }
      
      







メインスレッドと実行ループ







C ++では、メインスレッドのようなものはありません。 しかし、ほとんどすべてのUIアプリケーションはこの概念に基づいて構築されています。 UIはメインスレッドからのみ変更できます。 そのため、実行ループを自分で整理するか、既存のループに組み込む必要があります。



最初に、「メインスレッド」用に個別のキューを作成します。
メインキュー
  struct main_queue : queue{ virtual void async(dispatch::function task) const override; main_queue(): queue(0) {}; }; std::shared_ptr<queue> queue::main_queue(){ return std::static_pointer_cast<dispatch::queue>(std::make_shared<dispatch::main_queue>()); }
      
      





そして、非同期メソッドでは、タスクを追加します
別の行
  void main_queue::async(dispatch::function task) const { auto pool = thread_pool::shared_pool(); std::unique_lock<std::mutex> lock(pool->main_thread_mutex); pool->main_queue.push(task); if (pool->main_loop_need_update != nullptr) pool->main_loop_need_update(); }
      
      





さて、メインスレッドから呼び出される関数が必要です。
コード
  void process_main_loop() { auto pool = thread_pool::shared_pool(); std::unique_lock<std::mutex> lock(pool->main_thread_mutex); while (!pool->main_queue.empty()) { auto task = pool->main_queue.front(); pool->main_queue.pop(); task(); } }
      
      









質問は2つだけです。「どのように?」と「なぜ?」



まず、「なぜ?」:C ++は、クロスプラットフォームソフトウェアの作成によく使用されます。 移植性のために、多くの便利なものを廃棄する必要があります。 GCDは、非同期キューを管理するためのシンプルで直感的で便利な方法を提供する非常に便利なライブラリです。

「どのように?」という質問に対する明確な答えはありません。 さまざまな方法でranlupをウェッジできます。 多くのシステムは、このためのAPIを提供します。 たとえば、iOSには「performSelectorOnMainThread:」があります。 ディスパッチ:: set_main_loop_process_callbackでコールバックを設定するだけです。

 -(void)dispatchMainThread{ dispatch::process_main_loop(); } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ dispatch::set_main_loop_process_callback([=]{ [self performSelectorOnMainThread:@selector(dispatchMainThread) withObject:nil waitUntilDone:NO]; }); return YES; }
      
      





私たちが自分で子孫を組織する場合、次のようなことができます:
  void main_loop(dispatch::function main_loop_function); void main_loop(dispatch::function main_loop_function){ auto main_queue = queue::main_queue(); while (!thread_pool::shared_pool()->stop) { main_queue->async(main_loop_function); process_main_loop(); } }
      
      









そして今、実際には、それが考案されたために:



6つのキューを作成し、それぞれに6つのタスクを詰め込みます。

  auto main_thread_id = std::this_thread::get_id(); for (unsigned task = 0; task < 6; ++task) for (unsigned priority = 0; priority < 6; ++priority){ dispatch::queue(priority).async([=]{ assert(std::this_thread::get_id() != main_thread_id); std::string task_string = std::to_string(task); std::string palceholder(1+priority*5, ' '); dispatch::queue::main_queue()->async([=]{ assert(std::this_thread::get_id() == main_thread_id); std::cout << palceholder << task_string << std::endl; }); }); }
      
      





この写真について
 0 1 0 0 2 1 1 3 2 2 4 3 3 5 4 4 0 5 5 1 0 0 2 1 1 3 2 2 4 3 3 5 4 4 5 5
      
      





「列」はキューです。 右側にあるほど、キューの優先度は高くなります。 この行はメインストリームのコールバックです。


さて、iOS用のコード:
  for (int i = 0; i < 20; ++i){ dispatch::queue(dispatch::QUEUE_PRIORITY::DEFAULT).async([=]{ NSAssert(![NSThread isMainThread], nil); std::string first_string = std::to_string(i); dispatch::queue::main_queue()->async([=]{ NSAssert([NSThread isMainThread], nil); std::string second_string = std::to_string(i+1); std::cout << first_string << " -> " << second_string << std::endl; [self.tableView reloadData]; //  -  UI. ,        }); }); }
      
      







おわりに



結論はありません。 このバイクは、C ++ 11でマルチスレッドをテストするためだけに作成されました。 コードはgithub提示されたあまり良くないC ++コードの200行をわずかに上回っています。 clang ++ 3.3、g ++-4.7 / g ++-4.8、および2012 Visual Studioコンパイラでテストされました。 つまり、メインコンパイラはすでにC ++ 11を十分にサポートしています。



Py.Sy. 私の自転車を書くことを求めて、私は彼らが軍事プロジェクトで使用することを勧めません。 一方、自転車は他にどのように深刻なものに変わることができますか?



さて、自転車を何台か、私は記事のどこに押し込むかは考えていませんでした











All Articles