golang context
12006 ワード
go 1.7以上のcontextパッケージは正式に公式ライブラリに登録されているので、
上記の実装(非最良の実装)では、
上記のタイムアウトはよく見えますが、別のスレッドは使用されていませんが、問題も非常に大きいです.グローバル変数を使用 までしか正確ではありません.
では,グローバル変数を用いる問題を無視して,上記のコードを改良して時間精度の問題を解決する.
以上のコードに基づいて、
上記のコードは、スレッド ではなく、より軽量な親 サブ は、 を取得するより多くの終了ステータス結果を提供する.グローバル変数およびグローバルな信号処理を使用しない 詳細はgolang contextを参照してください.公式には各
C言語と同じ
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()
メソッドを使用して、より詳細な終了ステータス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
の使用も同様であり、公式文書を参照すればよい.