skynet版本ad06d232a6e54179d1615241b273bc18ab08805c
为例,对源码进行阅读。
在skynet-src/skynet_main.c中有
int main(int argc, char *argv[]) {
const char * config_file = NULL ;
if (argc > 1) {
config_file = argv[1];
} else {
fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
"usage: skynet configfilename\n");
return 1;
}
skynet_globalinit();
skynet_env_init();
sigign();
struct skynet_config config;
#ifdef LUA_CACHELIB
// init the lock of code cache
luaL_initcodecache();
#endif
struct lua_State *L = luaL_newstate();
luaL_openlibs(L); // link lua lib
int err = luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");
assert(err == LUA_OK);
lua_pushstring(L, config_file);
err = lua_pcall(L, 1, 1, 0);
if (err) {
fprintf(stderr,"%s\n",lua_tostring(L,-1));
lua_close(L);
return 1;
}
_init_env(L);
config.thread = optint("thread",8);
config.module_path = optstring("cpath","./cservice/?.so");
config.harbor = optint("harbor", 1);
config.bootstrap = optstring("bootstrap","snlua bootstrap");
config.daemon = optstring("daemon", NULL);
config.logger = optstring("logger", NULL);
config.logservice = optstring("logservice", "logger");
config.profile = optboolean("profile", 1);
lua_close(L);
skynet_start(&config);
skynet_globalexit();
return 0;
}
从skynet的main函数开始阅读源码。
首先是
const char * config_file = NULL ;
if (argc > 1) {
config_file = argv[1];
从命令行的argv[1]中获取配置文件
./skynet examples/config
平时就像这样运行skynet(第二个参数是配置文件相对或者绝对路径)
void skynet_globalinit(void) {
G_NODE.total = 0;
G_NODE.monitor_exit = 0;
G_NODE.init = 1;
if (pthread_key_create(&G_NODE.handle_key, NULL)) {
fprintf(stderr, "pthread_key_create failed");
exit(1);
}
// set mainthread's key
skynet_initthread(THREAD_MAIN);
}
在文件skynet_server.c中
struct skynet_node {
int total;
int init;
uint32_t monitor_exit;
pthread_key_t handle_key;
bool profile; // default is off
};
static struct skynet_node G_NODE;
void skynet_initthread(int m) {
uintptr_t v = (uint32_t)(-m);
pthread_setspecific(G_NODE.handle_key, (void *)v);
}
这就是skynet_globalinit
做的事情。
解读:
通过搜索,G_NODE只在skynet/skynet-src/skynet_server.c
中使用。
在main函数中调用skynet_globalinit
这里涉及到线程中特有的线程存储,用到以下三个函数:
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
在多线程程序中,所有线程共享程序中的变量。现在有一全局变量,所有线程都可以使用它,改变它的值。
而如果每个线程希望能单独拥有它,那么就需要使用线程存储了。
表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的。这就是线程存储的意义。
下面说一下线程存储的具体用法。
-
创建一个类型为 pthread_key_t 类型的变量。
-
调用 pthread_key_create() 来创建该变量。
该函数有两个参数,第一个参数就是上面声明的 pthread_key_t 变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。 -
当线程中需要存储特殊值的时候,可以调用 pthread_setspcific() 。该函数有两个参数,第一个为前面声明的 pthread_key_t 变量,第二个为 void* 变量,这样你可以存储任何类型的值。
-
如果需要取出所存储的值,调用 pthread_getspecific() 。该函数的参数为前面提到的 pthread_key_t 变量,该函数返回 void * 类型的值。
现在我们再看看:
struct skynet_node {
int total;
int init;
uint32_t monitor_exit;
pthread_key_t handle_key;
bool profile; // default is off
};
static struct skynet_node G_NODE;
...
uint32_t
skynet_current_handle(void) {
if (G_NODE.init) {
void * handle = pthread_getspecific(G_NODE.handle_key);
return (uint32_t)(uintptr_t)handle;
} else {
uint32_t v = (uint32_t)(-THREAD_MAIN);
return v;
}
}
...
void skynet_globalinit(void) {
G_NODE.total = 0;
G_NODE.monitor_exit = 0;
G_NODE.init = 1;
if (pthread_key_create(&G_NODE.handle_key, NULL)) {
fprintf(stderr, "pthread_key_create failed");
exit(1);
}
// set mainthread's key
skynet_initthread(THREAD_MAIN);
}
...
void skynet_initthread(int m) {
uintptr_t v = (uint32_t)(-m);
pthread_setspecific(G_NODE.handle_key, (void *)v);
}
这三个函数pthread_key_create、pthread_setspecific和pthread_getspecific都出现了,就是给每个线程带上了线程的私有变量。
重点看这个函数:
uint32_t
skynet_current_handle(void) {
if (G_NODE.init) {
void * handle = pthread_getspecific(G_NODE.handle_key);
return (uint32_t)(uintptr_t)handle;
} else {
uint32_t v = (uint32_t)(-THREAD_MAIN);
return v;
}
}
在skynet/skynet-src/skynet_imp.h文件中
#define THREAD_MAIN 1
(uint32_t)(-1)表示的是1,111…111(共32个1),表示unsigned 32位整数的最大值。
实际上这里就是将skynet/skynet-src/skynet_imp.h
文件中的
#define THREAD_WORKER 0
#define THREAD_MAIN 1
#define THREAD_SOCKET 2
#define THREAD_TIMER 3
#define THREAD_MONITOR 4
通过全工程搜索的可以看到skynet_initthread调用的地方:
这五个宏定义,0到4,分别与5个线程(其中一个是主线程)绑定起来,然后再利用uint32_t skynet_current_handle(void)
获取线程的指针。
参考:
pthread_key_t和pthread_key_create()详解
为什么unsigned (-1)表示无符号整数的最大值