なるべく簡単なロジックで、おおむね被らないようなIDをスレッドごとに吐けないだろうか、それも同じスレッドなら何度呼び出しても同じIDになるやつ(=キャッシュが要らない)、と思ってこういうのを考えた。
とりあえずコード:
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/syscall.h> pid_t gettid(void) { return syscall(SYS_gettid); } unsigned long long getthreadseq(void) { pid_t tid = gettid(); char fmt[] = "/proc/self/task/%d/stat"; char statpath[sizeof(fmt)+10]; unsigned long long starttime; (void)snprintf(statpath, sizeof(fmt)+10, fmt, tid); FILE *fp = fopen(statpath, "r"); int _nulld; unsigned _nullu; unsigned long _nulllu; long _nullld; char _nullc; char _nulls[65536]; int r = fscanf(fp, "%d %s %c %d %d " "%d %d %d %u %lu " "%lu %lu %lu %lu %lu " "%ld %ld %ld %ld %ld " "%ld %llu %s", &_nulld, _nulls, &_nullc, &_nulld, &_nulld, &_nulld, &_nulld, &_nulld, &_nullu, &_nulllu, &_nulllu, &_nulllu, &_nulllu, &_nulllu, &_nulllu, &_nullld, &_nullld, &_nullld, &_nullld, &_nullld, &_nullld, &starttime, _nulls ); fclose(fp); if(r < 0) { perror("fscanf"); exit(1); } // printf("tid = %d, starttime = %llu\n", tid, starttime); return (starttime << 18) | tid; } #include <pthread.h> void* thread_main(void* args) { unsigned long long id = getthreadseq(); printf("seq = %llu\n", id); sleep(3); unsigned long long id2 = getthreadseq(); printf("seq(again) = %llu | old = %llu\n", id2, id); return NULL; } int main(int argc, char *argv[]) { int thcount = 10; if(argc > 1) { thcount = atol(argv[1]); if(thcount <= 0) { exit(127); } } pthread_t tids[thcount]; for(int i=0; i<thcount; ++i){ pthread_t t; pthread_create(&t, NULL, thread_main, (void *)NULL); tids[i] = t; } for(int i=0; i<thcount; ++i){ pthread_join(tids[i], NULL); } return 0; }
動かす
cc threadseq.c -o threadseq -lpthread
$ ./threadseq seq = 280515858282 seq = 280515858284 seq = 280515858283 seq = 280515858281 seq = 280515858285 seq = 280515858287 seq = 280515858286 seq = 280515858288 seq = 280515858289 seq = 280515858290 seq(again) = 280515858284 | old = 280515858284 seq(again) = 280515858287 | old = 280515858287 seq(again) = 280515858282 | old = 280515858282 seq(again) = 280515858281 | old = 280515858281 seq(again) = 280515858285 | old = 280515858285 seq(again) = 280515858283 | old = 280515858283 seq(again) = 280515858289 | old = 280515858289 seq(again) = 280515858288 | old = 280515858288 seq(again) = 280515858286 | old = 280515858286 seq(again) = 280515858290 | old = 280515858290
Uniqueかどうか
$ ./threadseq 10000 | grep -v again | sort | uniq | wc -l 10000
やったこと
- Thread ID を取得する
gettid(2)
syscall で取る、glibcがラッパー関数を作っていないのでsyscall(SYS_gettid)
で自分で定義する
/proc/self/task/tid/stat
の 22 番目の項目が「starttime」という値で、起動時間により変わる何かなのでそれも取得する- Man page of PROC
- システムが起動した時刻からのtick回数になる
- Thread ID とstarttimeを用いてunsigned long longの値を生成する
- 雑には
(starttime << 18) | tid
- Thread ID はいつか一周するが、同じstarttimeで同じThread IDになるスレッドが生成されることが極めて稀であることを利用する
- 18 という左シフトは pid_max で変えると良いと思う
- 雑には
実際どうか
ほぼ同時に50000とか100000スレッド作っても被らないIDが出せているように見える。理屈の上では、同じ starttime
の間に pid_max 個以上のスレッドができるとかぶるので、たぶん、 kernel.pid_max
の値が大きい方が都合が良さそう(実験した環境は pid_max = 200000)。
$ ./threadseq 50000 | grep -v again | sort | uniq -c | head 1 seq = 293374145140 1 seq = 293374145141 1 seq = 293374145142 1 seq = 293374145143 1 seq = 293374145144 1 seq = 293374145145 1 seq = 293374145146 1 seq = 293374145147 1 seq = 293374145148 1 seq = 293374145149 $ ./threadseq 100000 | grep -v again | sort | uniq -c | head 1 seq = 293718128074 1 seq = 293718128075 1 seq = 293718128076 1 seq = 293718128077 1 seq = 293718390222 1 seq = 293718390223 1 seq = 293718390224 1 seq = 293718390225 1 seq = 293718390226 1 seq = 293718390227
ユニークなIDを発行する手段はいくらでもあるのだが、これについてはそのスレッドなら いつ呼んでも同じ値になる 、という便利な性質がある。パフォーマンス計測時のトレース時のIDとか、最悪被っても影響が小さいような何かに使えるかもしれない。
あと、ジャストアイデアなので考え方がアマ〜いという点があれば是非教えてください。