スカイネットソースコードのアクターライフサイクル管理

Skynetはマルチスレッドに基づいています。各アクターは個別のスレッドによってスケジュールされ、各アクターは他のアクターを殺したり、他のアクターにメッセージを送信したり、アクターを作成したりできます。つまり、アクターは複数のスレッドによって保持される可能性があります。3つの問題があります。 :

アクターを同時に使用する場合、安全に解放する方法。
アクターが解放された後、プロセスを続行できるように、アクターが外部使用に対して無効であることを検出する方法。
メールボックス内のメッセージに要求応答セマンティクスがある場合、メッセージソースに通知されます。
フレームワークは、ハンドルマッピングと参照カウントを使用して、ポインターではなくsc(skynet_context)のハンドルを公開します。このモジュールは/skynet-src/skynet_handle.cに実装されており、ヘッダーファイルのインターフェイスから始めて特定の原則を分析します。

skynet_handle.h

1 #ifndef SKYNET_CONTEXT_HANDLE_H
 2 #define SKYNET_CONTEXT_HANDLE_H
 3 
 4 #include <stdint.h>
 5 
 6 // reserve high 8 bits for remote id
 7 #define HANDLE_MASK 0xffffff
 8 #define HANDLE_REMOTE_SHIFT 24
 9 
10 struct skynet_context;
11 
12 uint32_t skynet_handle_register(struct skynet_context *);
13 int skynet_handle_retire(uint32_t handle);
14 struct skynet_context * skynet_handle_grab(uint32_t handle);
15 void skynet_handle_retireall();
16 
17 uint32_t skynet_handle_findname(const char * name);
18 const char * skynet_handle_namehandle(uint32_t handle, const char *name);
19 
20 void skynet_handle_init(int harbor);
21 
22 #endif

ハンドルはuint32_tの整数で、上位8ビットはリモートノードを表します(これはフレームワークに付属するクラスター機能であり、以降の分析ではこの部分は無視されます。まず、フレームワークのコアではなく、次に、このクラスター機能は推奨されません)。

その内部データ構造を見てみましょう。

define DEFAULT_SLOT_SIZE 4
#define MAX_SLOT_SIZE 0x40000000

struct handle_name {
    
    
    char * name;
    uint32_t handle;
};

struct handle_storage {
    
    
    struct rwlock lock;

    uint32_t harbor;
    uint32_t handle_index;
    int slot_size;
    struct skynet_context ** slot;
    
    int name_cap;
    int name_count;
    struct handle_name *name;
};

static struct handle_storage *H = NULL;

scの配列のように見えますが、何も表示されません。他のメソッドskynet_handle_registerを見てください。

 1 uint32_t
 2 skynet_handle_register(struct skynet_context *ctx) {
    
    
 3     struct handle_storage *s = H;
 4 
 5     rwlock_wlock(&s->lock);
 6     
 7     for (;;) {
    
    
 8         int i;
 9         for (i=0;i<s->slot_size;i++) {
    
    
10             uint32_t handle = (i+s->handle_index) & HANDLE_MASK;
11             int hash = handle & (s->slot_size-1);
12             if (s->slot[hash] == NULL) {
    
    
13                 s->slot[hash] = ctx;
14                 s->handle_index = handle + 1;
15 
16                 rwlock_wunlock(&s->lock);
17 
18                 handle |= s->harbor;
19                 return handle;
20             }
21         }
22         assert((s->slot_size*2 - 1) <= HANDLE_MASK);
23         struct skynet_context ** new_slot = skynet_malloc(s->slot_size * 2 * sizeof(struct skynet_context *));
24         memset(new_slot, 0, s->slot_size * 2 * sizeof(struct skynet_context *));
25         for (i=0;i<s->slot_size;i++) {
    
    
26             int hash = skynet_context_handle(s->slot[i]) & (s->slot_size * 2 - 1);
27             assert(new_slot[hash] == NULL);
28             new_slot[hash] = s->slot[i];
29         }
30         skynet_free(s->slot);
31         s->slot = new_slot;
32         s->slot_size *= 2;
33     }
34 }

このメソッドは、scのハンドルマッピングを追加することです。

コードの観点からは、これは一種のハッシュマッピングであり、読み取り/書き込みロックを使用してスレッドの安全性を確保します。9〜21行目は、自己増加整数であるハッシュ値の選択プロセスであり、計算されるたびに1ずつ増加します。handle_indexはカウンターとして使用され、モジュラスはscにマップするために使用されます。アレイ。2番目の検出方法を使用して競合を解決します。9行目のループにより、競合検出がアレイ全体をカバーするようになります。

22行目は、配列がいっぱいであることを意味します。この時点で、元の配列は2倍になり、ハンドルはモジュロになり、新しい配列に再度マップされます。このハッシュルールには2つの利点があります:1。ハッシュ値が繰り返されない2.検索プロセスは本当にO(1)です。

22行目とhandle_indexの変更から、この関数は2つの前提に基づいていることがわかります。1。配列のサイズが0xffffffを超えないこと。2。handle_indexはオーバーフローを処理しない。オーバーフローしないと想定されます。個人的には、オーバーフローの状況に対処するには、handle_indexの方が適していると思います。0xffffffより大きい場合は、1に設定します。

skynet_handle_grabをもう一度見てみましょう。

1 struct skynet_context * 
 2 skynet_handle_grab(uint32_t handle) {
    
    
 3     struct handle_storage *s = H;
 4     struct skynet_context * result = NULL;
 5 
 6     rwlock_rlock(&s->lock);
 7 
 8     uint32_t hash = handle & (s->slot_size-1);
 9     struct skynet_context * ctx = s->slot[hash];
10     if (ctx && skynet_context_handle(ctx) == handle) {
    
    
11         result = ctx;
12         skynet_context_grab(result);
13     }
14 
15     rwlock_runlock(&s->lock);
16 
17     return result;
18 }

この関数の機能は、ハンドルに従って対応するscを検索し、ハンドルが無効な場合はNULLを返すことです。検索は読み取りロックです。検索プロセスは非常に簡単です。ハンドルのモジュロを取り、次のことを判断します。インデックスの要素のハンドルは一貫しています。参照カウントは、このモジュールではなくscに格納されます。実際、より純粋にするために、このモジュールに配置する必要があります。scでは、それ自体を解放する方法を知っているだけで済みます。検索が成功すると、sc(skynet_context_grab)の数が増えます。

skynet_handle_retireを見てみましょう:

 1 int
 2 skynet_handle_retire(uint32_t handle) {
    
    
 3     int ret = 0;
 4     struct handle_storage *s = H;
 5 
 6     rwlock_wlock(&s->lock);
 7 
 8     uint32_t hash = handle & (s->slot_size-1);
 9     struct skynet_context * ctx = s->slot[hash];
10 
11     if (ctx != NULL && skynet_context_handle(ctx) == handle) {
    
    
12         s->slot[hash] = NULL;
13         ret = 1;
14         int i;
15         int j=0, n=s->name_count;
16         for (i=0; i<n; ++i) {
    
    
17             if (s->name[i].handle == handle) {
    
    
18                 skynet_free(s->name[i].name);
19                 continue;
20             } else if (i!=j) {
    
    
21                 s->name[j] = s->name[i];
22             }
23             ++j;
24         }
25         s->name_count = j;
26     } else {
    
    
27         ctx = NULL;
28     }
29 
30     rwlock_wunlock(&s->lock);
31 
32     if (ctx) {
    
    
33         // release ctx may call skynet_handle_* , so wunlock first.
34         skynet_context_release(ctx);
35     }
36 
37     return ret;
38 }

この関数の機能は、参照カウントをデクリメントするのではなく、ハンドルをマップ解除することです。

特定の実装には2つのステップがあります:1。ハンドルに対応するスロットをクリアし、skynet_context_releaseを呼び出します。2。登録された名前がある場合は、対応するノードを削除します。

実際、このモジュールにscの解放の制御を配置する方がよいでしょう。

残りの方法はハンドル命名のサポートであり、名前マッピングは配列に格納され、辞書式順序でソートされ、検索時にはバイナリ検索方法が使用されます。

これで、scライフサイクルの特定のシナリオを見ることができます。2つの場所を見てください。

メッセージディスパッチオフィスのskynet_context_message_dispatch関数で
は、scの外部インターフェイスは主にskynet_commandです
。skynet_context_message_dispatch(/skynet-src/skynet_server.cの285行目)で確認できます

struct skynet_context * ctx = skynet_handle_grab(handle);
    if (ctx == NULL) {
    
    
        struct drop_t d = {
    
     handle };
        skynet_mq_release(q, drop_message, &d);
        return skynet_globalmq_pop();
    }

skynet_handle_grabを介して、SCは無効であり、最初に発生した問題2を解決します。scの他の外部インターフェースもこの判断を下しました。

次に、残りは質問1、安全な解放の質問です。sc、cmd_exit、cmd_killの外部リリースインターフェイスを見てください。これらはすべてhandle_exitです。

 1 static void
 2 handle_exit(struct skynet_context * context, uint32_t handle) {
    
    
 3     if (handle == 0) {
    
    
 4         handle = context->handle;
 5         skynet_error(context, "KILL self");
 6     } else {
    
    
 7         skynet_error(context, "KILL :%0x", handle);
 8     }
 9     if (G_NODE.monitor_exit) {
    
    
10         skynet_send(context,  handle, G_NODE.monitor_exit, PTYPE_CLIENT, 0, NULL, 0);
11     }
12     skynet_handle_retire(handle);
13 }

この関数は最終的にskynet_handle_retireを呼び出し、ハンドルマッピングを解放した後、skynet_context_releaseを呼び出します。

skynet_context_releaseを見てください:

1 static void 
 2 delete_context(struct skynet_context *ctx) {
    
    
 3     if (ctx->logfile) {
    
    
 4         fclose(ctx->logfile);
 5     }
 6     skynet_module_instance_release(ctx->mod, ctx->instance);
 7     skynet_mq_mark_release(ctx->queue);
 8     CHECKCALLING_DESTROY(ctx)
 9     skynet_free(ctx);
10     context_dec();
11 }
12 
13 struct skynet_context * 
14 skynet_context_release(struct skynet_context *ctx) {
    
    
15     if (ATOM_DEC(&ctx->ref) == 0) {
    
    
16         delete_context(ctx);
17         return NULL;
18     }
19     return ctx;
20 }

参照カウントが0になると、scが解放されるため、質問1は次のように保証されます。

handle_exitを呼び出した後、2つの状況があります。

1.他のロジックフローはすでにscを取得しているため、参照カウントは0より大きくなければならず、この時点ではscは解放されません。最後のロジックフローが参照カウントをデクリメントすると解放されます。これは安全です。

2. scが解放され、他のロジックフローがskynet_handle_grabを開始します。ハンドルマッピングが削除されているため、すべての検索が無効になります。ロジックフローはこれを認識し、安全な判断を下すことができます。

scが解放されると、メールボックス(message_queue)は解放されず、skynet_mq_mark_releaseのみが呼び出されて解放フラグが設定されます。その後、どこで解放されますか?最初にそのような状況について考えてみましょう。scが解放され、メールボックスが解放されない場合、skynet_handle_grabは検索に失敗し、メールボックスは引き続きレベル1キューにあります。その後、解放場所はskynet_context_message_dispatchにのみ存在できます。見てみましょう。 back。これは、scが無効であると判断されたブランチでskynet_mq_releaseによって解放されたメールボックスと呼ばれます。

メールボックスを個別にリリースし、scと一緒にリリースしないようにする必要があるのはなぜですか?scは参照カウントによって解放されるため、解放のタイミングが明確ではなく、論理フローにある可能性があります。メッセージスケジューリングでレベル1キューにプッシュバックする必要があるかどうかを判断できないため、独立している必要があります。 。

問題3だけが残っています。メールボックスが解放されたときにメッセージがどのように処理されるかを確認する必要があります。/skynet-src/skynet_server.cのdrop_message内:

static void
drop_message(struct skynet_message *msg, void *ud) {
    
    
    struct drop_t *d = ud;
    skynet_free(msg->data);
    uint32_t source = d->handle;
    assert(source);
    // report error to the message source
    skynet_send(NULL, source, msg->source, PTYPE_ERROR, 0, NULL, 0);
}

これは、メッセージソースにPTYPE_ERRORを送信することで解決されるため、応答を受信することを期待しているscは、中断されたプロセスを終了する機会があります。しかし、質問があります。応答するときにセッションを持ってきてみませんか?ソースでメールボックスを見つける必要がありますか?ニュースが配信されるときにこれを見てください。

gcがない場合、マルチスレッドプログラミングでは、リソースを安全に解放する方法が直面しなければならない問題です。これは通常、別のモジュールで個別に解決されます。2つの一般的な方法があります。

この記事では、マッピングと参照カウントを処理します。スマートポインタは通常c ++で使用され、参照カウントは必須の保証としてデストラクタとコピーコンストラクタを介して自動的に加算および減算されます。個人的には前者の方が柔軟だと思います。
リリースされたときにのみマークを付け、特定の頻度で定期的にリソースを再利用します。

今すぐスカイネットを学ぶにはクリックして
ここに画像の説明を挿入
ください。スカイネットの詳細については、グループに参加してください:832218493無料!

おすすめ

転載: blog.csdn.net/lingshengxueyuan/article/details/111653972