golang context

12006 ワード

go 1.7以上のcontextパッケージは正式に公式ライブラリに登録されているので、import "context"だけでいいです.go 1.6および以下のバージョンでは、import "golang.org/x/net/context"なぜcontextが必要なのでしょうか.linuxでC言語を用いて実装されたcancel contextを見ることができます.
#include 
#include 
#include 

typedef struct cancel_context
{
  int is_exit;
  // pthread_mutex_t mutex;
  pthread_rwlock_t rwlock;
} cancel_context_t;

void *thr_fn(void *arg)
{
  cancel_context_t *p_ctx = (cancel_context_t *) arg;
  if(p_ctx == NULL) pthread_exit((void *)-1);

  int is_exit;
  int cnt = 0;
  while(1)
  {
    // pthread_mutex_lock(&p_ctx->mutex);
    pthread_rwlock_rdlock(&p_ctx->rwlock);
    is_exit = p_ctx->is_exit;
    // pthread_mutex_unlock(&p_ctx->mutex);
    pthread_rwlock_unlock(&p_ctx->rwlock);

    if(is_exit) break;

    printf("thr_fn is busing, %d
", ++cnt); usleep(100); } printf("thr_fn, cancelled by exit_fn
"); pthread_exit((void *)0); } void *cancel_fn(void *arg) { cancel_context_t *p_ctx = (cancel_context_t *) arg; if(p_ctx == NULL) pthread_exit((void*)-1); usleep(1000); // pthread_mutex_lock(&p_ctx->mutex); pthread_rwlock_wrlock(&p_ctx->rwlock); p_ctx->is_exit = 1; // pthread_mutex_unlock(&p_ctx->mutex); pthread_rwlock_unlock(&p_ctx->rwlock); printf("thr_fn, you need to exit
"); pthread_exit((void *)0); } void main() { int err; pthread_t p_id, cancel_p_id; void *ret; Context ctx; ctx.is_exit = 0; // pthread_mutex_init(&ctx.mutex, NULL); pthread_rwlock_init(&ctx.rwlock, NULL); err = pthread_create(&p_id, NULL, thr_fn, (void *) &ctx); err = pthread_create(&cancel_p_id, NULL, cancel_fn, (void *) &ctx); pthread_join(p_id, NULL); pthread_join(cancel_p_id, NULL); // pthread_mutex_destroy(&ctx.mutex); pthread_rwlock_destroy(&ctx.rwlock); printf("main thread exit!!!
"); }

上記の実装(非最良の実装)では、cancelの機能を実装するために、sleepが待機する時間(あることを完了した後にthr_fnの終了状態を設定するシミュレーション)を使用した.同時にthr_fnでこの終了状態を絶えずチェックする.この方法には多くの欠陥があり、まず脱退状態を絶えず検査する必要がある.次に、一部のブロック非タイムアウト呼び出しには適用されません.次に、linuxでC言語を使用して実装されたtimeout contextを見てみましょう.
#include 
#include 
#include 
#include 

typedef struct timeout_context
{
  int is_exit;
  pthread_rwlock_t rwlock;
} timeout_context_t;

timeout_context_t ctx;

void *thr_fn(void *arg)
{
  timeout_context_t *p_ctx = (timeout_context_t *) arg;
  if(p_ctx == NULL) pthread_exit((void *)-1);

  int is_exit;
  int cnt = 0;
  while(1)
  {
    pthread_rwlock_rdlock(&p_ctx->rwlock);
    is_exit = p_ctx->is_exit;
    pthread_rwlock_unlock(&p_ctx->rwlock);

    if(is_exit) break;

    printf("thr_fn is busing, %d
", ++cnt); usleep(1000 * 100); } printf("thr_fn, cancelled by timer
"); pthread_exit((void *)0); } void timer(int sig) { if(SIGALRM == sig) { pthread_rwlock_wrlock(&ctx.rwlock); ctx.is_exit = 1; pthread_rwlock_unlock(&ctx.rwlock); printf("timeout
"); } return; } void main() { int err; pthread_t p_id; ctx.is_exit = 0; pthread_rwlock_init(&ctx.rwlock, NULL); signal(SIGALRM, timer); alarm(2); err = pthread_create(&p_id, NULL, thr_fn, (void *) &ctx); pthread_join(p_id, NULL); pthread_rwlock_destroy(&ctx.rwlock); printf("main thread exit!!!
"); }

上記のタイムアウトはよく見えますが、別のスレッドは使用されていませんが、問題も非常に大きいです.
  • グローバル変数を使用ctx
  • alarmメソッドは秒レベル
  • までしか正確ではありません.
    では,グローバル変数を用いる問題を無視して,上記のコードを改良して時間精度の問題を解決する.
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    typedef struct timeout_context
    {
      int is_exit;
      pthread_rwlock_t rwlock;
    } timeout_context_t;
    
    timeout_context_t ctx;
    
    void *thr_fn(void *arg)
    {
      timeout_context_t *p_ctx = (timeout_context_t *) arg;
      if(p_ctx == NULL) pthread_exit((void *)-1);
    
      int is_exit;
      int cnt = 0;
      while(1)
      {
        pthread_rwlock_rdlock(&p_ctx->rwlock);
        is_exit = p_ctx->is_exit;
        pthread_rwlock_unlock(&p_ctx->rwlock);
    
        if(is_exit) break;
    
        printf("thr_fn is busing, %d
    ", ++cnt); usleep(100); } printf("thr_fn, cancelled by timer
    "); pthread_exit((void *)0); } void timer(int sig) { if(SIGALRM == sig) { pthread_rwlock_wrlock(&ctx.rwlock); ctx.is_exit = 1; pthread_rwlock_unlock(&ctx.rwlock); printf("timeout
    "); } return; } void main() { int err; pthread_t p_id; ctx.is_exit = 0; pthread_rwlock_init(&ctx.rwlock, NULL); signal(SIGALRM, timer); struct itimerval tick; bzero(&tick, sizeof(tick)); tick.it_value.tv_sec = 0; tick.it_value.tv_usec = 1000; // tick.it_interval.tv_sec = 0; // tick.it_interval.tv_usec = 1000; err = pthread_create(&p_id, NULL, thr_fn, (void *) &ctx); setitimer(ITIMER_REAL, &tick, NULL); pthread_join(p_id, NULL); pthread_rwlock_destroy(&ctx.rwlock); printf("main thread exit!!!
    "); }
    signal+settimerを用いて実現され、微妙なレベルまで正確に行うことができる.悪くないようですが、現実では、同時制御は簡単ではありません.thr_fnに新しいスレッドが作成されている場合、作成された新しいスレッドは親スレッドより先に終了してリソースの正しい解放を保証する必要があります.親スレッドが終了する前にサブスレッドが終了するのを待つだけでよく、サブスレッドにcontextを渡す必要があります.コードは以下の通りです.
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    typedef struct timeout_context
    {
      int is_exit;
      pthread_rwlock_t rwlock;
      timeout_context_t *parent;
    } timeout_context_t;
    
    timeout_context_t ctx;
    
    void *child_thr_fn(void *arg)
    {
      timeout_context_t *p_ctx = (timeout_context_t *) arg;
      if(p_ctx == NULL) pthread_exit((void *)-1);
    
      int is_exit;
      int cnt = 0;
      while(1)
      {
        pthread_rwlock_rdlock(&p_ctx->rwlock);
        is_exit = p_ctx->is_exit;
        pthread_rwlock_unlock(&p_ctx->rwlock);
    
        if(is_exit) break;
    
        printf("child_thr_fn is busing, %d
    ", ++cnt); usleep(100); } printf("child_thr_fn, cancelled by timer
    "); pthread_exit((void *)0); } void *parent_thr_fn(void *arg) { timeout_context_t *p_ctx = (timeout_context_t *) arg; if(p_ctx == NULL) pthread_exit((void *)-1); int err; pthread_t p_id; err = pthread_create(&p_id, NULL, child_thr_fn, (void *) p_ctx); int is_exit; int cnt = 0; while(1) { pthread_rwlock_rdlock(&p_ctx->rwlock); is_exit = p_ctx->is_exit; pthread_rwlock_unlock(&p_ctx->rwlock); if(is_exit) break; printf("parent_thr_fn is busing, %d
    ", ++cnt); usleep(100); } pthread_join(p_id, NULL); printf("parent_thr_fn, cancelled by timer
    "); pthread_exit((void *)0); } void timer(int sig) { if(SIGALRM == sig) { pthread_rwlock_wrlock(&ctx.rwlock); ctx.is_exit = 1; pthread_rwlock_unlock(&ctx.rwlock); printf("timeout
    "); } return; } void main() { int err; pthread_t p_id; ctx.is_exit = 0; pthread_rwlock_init(&ctx.rwlock, NULL); signal(SIGALRM, timer); struct itimerval tick; bzero(&tick, sizeof(tick)); tick.it_value.tv_sec = 0; tick.it_value.tv_usec = 1000; // tick.it_interval.tv_sec = 0; // tick.it_interval.tv_usec = 1000; err = pthread_create(&p_id, NULL, parent_thr_fn, (void *) &ctx); setitimer(ITIMER_REAL, &tick, NULL); pthread_join(p_id, NULL); pthread_rwlock_destroy(&ctx.rwlock); printf("main thread exit!!!
    "); }

    以上のコードに基づいて、deadline contextを実装することもできます.次に、value contextの実装(要求ベースのコンテキストデータ転送、key/valueコンテキストを実装するために使用可能)を見てみましょう.C言語にはHashmapライブラリがないため、ここではc_が使用されています.hashmap.
    #include 
    #include 
    #include 
    #include 
    
    #include "hashmap.h"
    
    #define KEY_MAX_LENGTH (256)
    #define VALUE_MAX_LENGTH (1024*1024)
    #define KEY_COUNT (1024*1024)
    
    typedef struct value_context
    {
      map_t h;
    } value_context_t;
    
    typedef struct data
    {
      char key[KEY_MAX_LENGTH];
      int n;
      double d;
      char s[VALUE_MAX_LENGTH];
    } data_t;
    
    void value_context_init(value_context_t *p_ctx)
    {
      assert(p_ctx != NULL);
    
      p_ctx->h = hashmap_new();
    }
    
    void value_context_set_int(value_context_t *p_ctx, char *key, int n)
    {
      data_t *data = malloc(sizeof(data_t));
      memcpy(data->key, key, strlen(key));
      data->n = n;
    
      hashmap_put(p_ctx->h, data->key, data);
    }
    
    void value_context_set_double(value_context_t *p_ctx, char *key, double d)
    {
      data_t *data = malloc(sizeof(data_t));
      memcpy(data->key, key, strlen(key));
      data->d = d;
    
      hashmap_put(p_ctx->h, data->key, data);
    }
    
    void value_context_set_string(value_context_t *p_ctx, char *key, char *s)
    {
      data_t *data = malloc(sizeof(data_t));
      memcpy(data->key, key, strlen(key));
      int len = strlen(s);
      memcpy(data->s, s, len > VALUE_MAX_LENGTH ? VALUE_MAX_LENGTH : len);
    
      hashmap_put(p_ctx->h, data->key, data);
    }
    
    int value_context_get_int(value_context_t *p_ctx, char *key)
    {
      assert(p_ctx != NULL);
      data_t* value = NULL;
      hashmap_get(p_ctx->h, key, (void**)(&value));
      assert(value != NULL);
      return value->n;
    }
    
    double value_context_get_double(value_context_t *p_ctx, char *key)
    {
      assert(p_ctx != NULL);
      data_t* value = NULL;
      hashmap_get(p_ctx->h, key, (void**)(&value));
      assert(value != NULL);
      return value->d;
    }
    
    char* value_context_get_string(value_context_t *p_ctx, char *key)
    {
      assert(p_ctx != NULL);
      data_t* value = NULL;
      hashmap_get(p_ctx->h, key, (void**)(&value));
      assert(value != NULL);
      return value->s;
    }
    
    void filter(value_context_t *p_ctx)
    {
      value_context_set_string(p_ctx, "username", "zouqilin");
    }
    
    void handler(value_context_t *p_ctx)
    {
      char *s = value_context_get_string(p_ctx, "username");
      printf("context get username: %s
    ", s); } void main() { value_context_t ctx; value_context_init(&ctx); filter(&ctx); handler(&ctx); // here we need to free resource }

    上記のコードは、handler処理の前にfilterが呼び出され、usernameが設定されることを実現しているので、handler処理時にこの設定の値を取得することができる.このように大回りしてgolangが加えたcontextの必要性を説明した.次に、同時プログラミングおよびリクエスト処理におけるcontextの利点について説明する.golangでは,WithCancel,WithDeadline,WithTimeoutまたはWithValueから新たなcontextが派生し,上記C言語コードの機能を実現する.
  • スレッド
  • ではなく、より軽量なgoroutine
  • contextがキャンセルされると、派生したすべてのcontextがキャンセルされます
  • サブgoroutineで使用可能select
  • context同時安全
  • は、Error()メソッドを使用して、より詳細な終了ステータス
  • を取得するより多くの終了ステータス結果を提供する.
  • グローバル変数およびグローバルな信号処理を使用しない
  • 詳細はgolang contextを参照してください.公式には各contextに使用例が提供されています.次にgolangを使用してcancel contextを実装し、コードがどのように簡潔でコンパクトであるかを見てみましょう.
    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func main() {
        var wg sync.WaitGroup
        wg.Add(1)
        ctx, cancel := context.WithCancel(context.Background())
    
        go func(ctx context.Context) {
            defer wg.Done()
            n := 0
            for {
                select {
                case 

    C言語と同じcancel contextを実現するのはかなり楽で、上述した時間精度はナノ秒レベルである.その他のcontextの使用も同様であり、公式文書を参照すればよい.