写在前面
名词解释
skynet 是一个多线程框架,功能按服务划分,正确区分各个名词十分重要。
- 线程:程序可以并行运行多少个执行序,一般指 worker 线程
- 服务:模块运行时的实例,多个服务可以并发执行
- 协程:调度最小单元,一个服务由多个协程组成
skynet 线程
-
skynet 关于线程的代码如下,一共有5种线程。
/* skynet_imp.h */ #define THREAD_WORKER 0 #define THREAD_MAIN 1 #define THREAD_SOCKET 2 #define THREAD_TIMER 3 #define THREAD_MONITOR 4
-
按启动顺序排序是:
- thread_mian (主线程)
- thread_monitor (监控线程)
- thread_timer (定时器线程)
- thread_socket (网络线程)
- thread_worker (工作线程)
-
配置里面的 thread 指的是 thread_worker 数量。
/* skynet_start.c */ static void start(int thread) { pthread_t pid[thread+3]; struct monitor *m = skynet_malloc(sizeof(*m)); memset(m, 0, sizeof(*m)); m->count = thread; m->sleep = 0; m->m = skynet_malloc(thread * sizeof(struct skynet_monitor *)); int i; for (i=0;i<thread;i++) { m->m[i] = skynet_monitor_new(); } if (pthread_mutex_init(&m->mutex, NULL)) { fprintf(stderr, "Init mutex error"); exit(1); } if (pthread_cond_init(&m->cond, NULL)) { fprintf(stderr, "Init cond error"); exit(1); } create_thread(&pid[0], thread_monitor, m); create_thread(&pid[1], thread_timer, m); create_thread(&pid[2], thread_socket, m); static int weight[] = { -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, }; struct worker_parm wp[thread]; for (i=0;i<thread;i++) { wp[i].m = m; wp[i].id = i; if (i < sizeof(weight)/sizeof(weight[0])) { wp[i].weight= weight[i]; } else { wp[i].weight = 0; } create_thread(&pid[i+3], thread_worker, &wp[i]); } for (i=0;i<thread+3;i++) { pthread_join(pid[i], NULL); } free_monitor(m); }
准备工作
首先要有一个编译好,而且工作正常的 skynet 。最好已经对服务间通信有一定了解。
代码演示
基本示例
我们将启用两个服务,一个 hello 服务,每秒打印一次 Hello, World! 。另一个是 main 服务,每秒打印一次 This is main.
-
主服务
-- main.lua local skynet = require "skynet" skynet.init(function() -- 启动 hello skynet.newservice("hello") end) skynet.start(function() skynet.fork(function() while true do -- 每秒打印一次 skynet.error("This is main.") skynet.sleep(100) end end) end)
-
hello 服务
-- hello.lua local skynet = require "skynet" skynet.start(function() skynet.fork(function() while true do -- 每秒打印一次 skynet.error("Hello, World!") skynet.sleep(100) end end) end)
-
运行结果
[:00000002] LAUNCH snlua bootstrap [:00000003] LAUNCH snlua launcher [:00000004] LAUNCH snlua cdummy [:00000005] LAUNCH harbor 0 4 [:00000006] LAUNCH snlua datacenterd [:00000007] LAUNCH snlua service_mgr [:00000008] LAUNCH snlua main [:00000009] LAUNCH snlua hello [:00000009] Hello, World! [:00000008] This is main. [:00000002] KILL self [:00000009] Hello, World! [:00000008] This is main. [:00000009] Hello, World! [:00000008] This is main. ...
结果为 main 服务和 hello 服务每秒打印一次。
示例变体一
-
将 hello 服务改成死循环
修改代码,验证服务间的独立性。
- main 服务不变
- hello 服务加入死循环代码
-- hello.lua local skynet = require "skynet" skynet.start(function() skynet.fork(function() while true do -- 每秒打印一次 skynet.error("Hello, World!") skynet.sleep(100) -- 进入死循环 while true do end end end) end)
-
运行结果
[:00000002] LAUNCH snlua bootstrap [:00000003] LAUNCH snlua launcher [:00000004] LAUNCH snlua cdummy [:00000005] LAUNCH harbor 0 4 [:00000006] LAUNCH snlua datacenterd [:00000007] LAUNCH snlua service_mgr [:00000008] LAUNCH snlua main [:00000009] LAUNCH snlua hello [:00000009] Hello, World! [:00000008] This is main. [:00000002] KILL self [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000008] This is main. [:00000000] A message from [ :00000000 ] to [ :00000009 ] maybe in an endless loop (version = 85) [:00000008] This is main. ...
结果为
- hello 服务只打印了一次
- main 服务正常执行,每秒打印一次
- monitor 还识别出来 hello 服务死循环了
示例变体二
-
启动两个 hello 服务,工作线程数改为 2
- hello 服务保持死循环不变
- 修改 main 服务,启动两个 hello 服务
- 修改配置,改成两个线程
-- main.lua local skynet = require "skynet" skynet.init(function() -- 启动 2 个 hello skynet.newservice("hello") skynet.newservice("hello") end) skynet.start(function() skynet.fork(function() while true do -- 每秒打印一次 skynet.error("This is main.") skynet.sleep(100) end end) end)
-
运行结果
[:00000002] LAUNCH snlua bootstrap [:00000003] LAUNCH snlua launcher [:00000004] LAUNCH snlua cdummy [:00000005] LAUNCH harbor 0 4 [:00000006] LAUNCH snlua datacenterd [:00000007] LAUNCH snlua service_mgr [:00000008] LAUNCH snlua main_ping [:00000009] LAUNCH snlua hello [:00000009] Hello, World! [:0000000a] LAUNCH snlua hello [:0000000a] Hello, World! [:00000008] This is main. [:00000002] KILL self
结果为
- hello 服务只打印了一次
- main 服务只打印了一次
- 少了一条“ maybe in an endless loop ”打印
-
为什么会少了一条 monitor 打印呢
因为 monitor 的打印是通过 logger 服务输出的。当所有 worker 线程进入了死循环, logger 便无法输出内容了。
结论
- 同一个模块的多个服务实例,可以占用多个线程,享有平等执行权。
- 其他服务不能抢占当前服务,只能等当前服务让出执行权。