0

ctrl_sockcontrol( ) 用と data transmission( )用の 2 つのソケットを保持する UDP ベースのサーバーを実装しようとしていますdata_sockctrl_sock問題は、常にアップリンクであり、ダウンリンクであるということですdata_sock。つまり、クライアントは を介し​​てデータの送信/停止を要求しctrl_sock、データは を介し​​てクライアントに送信されますdata_sock

ここでの問題は、モデルがコネクションレスであるため、サーバーは登録されたクライアントの情報のリスト (私はそれと呼んでいますpeers_context) を維持する必要があり、クライアントが停止を要求するまでデータを「盲目的に」プッシュできるようにすることです。このブラインド送信中に、クライアントは を介し​​てctrl_sock非同期的に制御メッセージをサーバーに送信できます。これらの情報は、最初の Request と Stop の他に、ファイル パーツの設定などにも使用できます。したがって、 はpeers_context非同期で更新する必要があります。ただし、 を介した送信はdata_sockこの構造に依存しているため、 と の間でpeers_context同期の問題が発生します。私の質問は、これら 2 つのソックスと、ctrl_sockdata_sockpeers_contextpeers_context大混乱を引き起こしません。ところで、 の更新はpeers_contextそれほど頻繁ではないため、要求と応答のモデルを避ける必要があります。

実装に関する私の最初の考慮事項は、ctrl_sock をメイン スレッド (リスナー スレッド) でdata_sock維持し、転送を他のスレッド (ワーカー スレッド) で維持することです。ただし、この場合は同期が難しいことがわかりました。たとえば、 でミューテックスを使用するとpeers_context、ワーカー スレッドが をロックするたびに、peers_contextを変更する必要があるときにリスナー スレッドがアクセスできなくなりますpeers_context。これは、ワーカー スレッドが際限なく動作するためです。一方、リスナー スレッドが を保持しpeers_contextて書き込みを行うと、ワーカー スレッドは読み取りに失敗してpeers_context終了します。誰か私にいくつかの提案をしてもらえますか?

ちなみに、実装はC言語のLinux環境で行っています。リスナースレッドだけはpeers_context時々変更する必要があり、ワーカースレッドは読み取るだけで済みます。心から感謝します!

4

1 に答える 1

1

競合が激しい場合はpeers_context、クリティカル セクションを短くする必要があります。あなたはミューテックスの使用について話しました。リーダーとライターのロックへの変更を既に検討し、それを拒否したと思います。一定のリーダーがライターを飢えさせたくないからです。これはどう?

peers_context次のようなa への間接参照である非常に小さな構造を作成します。

struct peers_context_handle {
    pthread_mutex_t ref_lock;
    struct peers_context *the_actual_context;
    pthread_mutex_t write_lock;
};

パケット送信者 (リーダー) と制御要求プロセッサ (ライター) は、常にpeers_mutexこの間接経由で にアクセスします。

前提: パケットの送信者は を変更したり、peers_context解放したりすることはありません。

Packer 送信者はハンドルを簡単にロックし、現在のバージョンの を取得してpeers_contextロックを解除します。

pthread_mutex_lock(&(handle->ref_lock));
peers_context = handle->the_actual_context;
pthread_mutex_unlock(&(handle->ref_lock));

(実際には、ポインターの逆参照は Linux がサポートするすべてのプラットフォームでアトミックであるため、メモリ バリアを導入すればロックを廃止することもできますが、メモリ バリアやその他の機能について詳しく調べる必要があるため、お勧めしません。低レベルのものであり、C も POSIX もそれが動作することを保証していません。)

リクエスト プロセッサは を更新せずpeers_context、コピーを作成して完全に置き換えます。それが彼らがクリティカルセクションを小さく保つ方法です。更新をシリアル化するために使用しますwrite_lockが、更新は頻繁ではないため、問題にはなりません。

pthread_mutex_lock(&(handle->write_lock));

/* Short CS to get the old version */
pthread_mutex_lock(&(handle->ref_lock));
old_peers_context = handle->the_actual_context;
pthread_mutex_unlock(&(handle->ref_lock));

new_peers_context = allocate_new_structure();
*new_peers_context = *old_peers_context;

/* Now make the changes that are requested */
new_peers_context->foo = 42;
new_peers_context->bar = 52;

/* Short CS to replace the context */
pthread_mutex_lock(&(handle->ref_lock));
handle->the_actual_context = new_peers_context;
pthread_mutex_unlock(&(handle->ref_lock));

pthread_mutex_unlock(&(handle->write_lock));

magic(old_peers_context);

キャッチは何ですか?これは、コードの最後の行にある魔法です。メモリ リークを避けるために の古いコピーを解放するpeers_context必要がありますが、そのコピーをまだ使用しているパケット送信者が存在する可能性があるため、解放することはできません。

このソリューションは、Linux カーネル内で使用されるRCUに似ています。すべてのパケット送信スレッドが休止状態になるまで待機する必要があります。これの実装は演習として残しておきます :-) しかし、ガイドラインは次のとおりです。

  • この関数は、解放されるキュー (ミューテックスによって保護されている必要があります) をmagic()追加します。old_peers_context
  • 1 つの専用スレッドがループ内でこのリストを解放します。
    1. 解放予定リストをロックします
    2. リストへのポインタを取得します
    3. リストを新しい空のリストに置き換えました
    4. 解放予定リストのロックを解除します
    5. 各ワーカースレッドに関連付けられたマークをクリアします
    6. すべてのマークが再設定されるのを待ちます
    7. 以前に取得した解放対象リストのコピー内の各項目を解放します
  • 一方、各ワーカー スレッドは、イベント ループのアイドル ポイント (つまり、パケットの送信やpeer_contexts.
于 2012-04-19T19:27:23.657 に答える