系列文章
- C/C++ Addons 入門 Hello world!
- C/C++ Addons 對象參數(shù)及回調(diào)函數(shù)
- C/C++ Addons 非阻塞多線程回調(diào)
- C/C++ Addons windows 下 .dll 動態(tài)鏈接庫 實用篇
- 這篇文章是解決上一篇文章碰到的 Sleep() 后線程
阻塞(休眠)
問題探索的解決方案
node.js 異步理解 (參考深入淺出 node.js)
- 簡單的交代下:
所謂 node.js 異步事件環(huán),底層使用了類似while(true)
的方式開了一個死循環(huán),然后在每次循環(huán)的時候去異步隊列中取出一個或多個待執(zhí)行的任務(wù)執(zhí)行。
待執(zhí)行任務(wù)來自node.js
提供的異步接口如:setTimeout
或fs
、http
模塊提供的異步方法等,這里不一一列出。
任何任務(wù)都需要線程去執(zhí)行,既然主線程不執(zhí)行他們那么這些非阻塞的任務(wù)是被怎樣執(zhí)行的呢?其實本質(zhì)上node.js
底層還是使用了多線程。
當(dāng)然windows
和*nix
平臺實現(xiàn)略有不同,windows
底層由操作系統(tǒng)接管,*nix
底層是node.js
自己實現(xiàn)的多線程任務(wù)池,這里不展開細(xì)講。
非阻塞
代碼實現(xiàn)
- C/C++
src/thread-cb.c
#include <stdio.h>
#include <windows.h>
#include <assert.h>
// 開啟 N-API 實驗性功能,如:多線程 napi_threadsafe_function
#define NAPI_EXPERIMENTAL
#include <node_api.h>
typedef struct{
napi_async_work work; // 保存線程任務(wù)的
napi_threadsafe_function tsfn; // 保存回調(diào)函數(shù)的
}Addon;
/** 調(diào)試報錯用的 */
static void catch_err(napi_env env, napi_status status) {
if (status != napi_ok) {
const napi_extended_error_info* error_info = NULL;
napi_get_last_error_info(env, &error_info);
printf("%s\n", error_info->error_message);
exit(0);
}
}
/** 調(diào)用 js-callback 用的 */
static void call_js(napi_env env, napi_value js_cb, void* context, void* data) {
(void)context; // 用不到它
(void)data; // 用不到它
if (env != NULL) {
napi_value undefined, js_prime;
napi_get_undefined(env, &undefined); // 創(chuàng)建一個 js 的 undefined
catch_err(env, napi_call_function(env,
undefined, // js 回調(diào)的 this 對象
js_cb, // js 回調(diào)函數(shù)句柄
0, // js 回調(diào)函數(shù)接受參數(shù)個數(shù)
NULL, // js 回調(diào)函數(shù)參數(shù)數(shù)組
NULL)); // js 回調(diào)函數(shù)中如果有 retrun,將會被 result 接受到,NULL 代表忽略
}
}
/** 執(zhí)行線程 */
static void execute_work(napi_env env, void* data) {
Addon* addon = (Addon*)data;
// 拿到 js-callback 函數(shù)
catch_err(env, napi_acquire_threadsafe_function(addon->tsfn));
// 延遲四秒執(zhí)行
Sleep(4000);
// 調(diào)用 js-callback 函數(shù)
catch_err(env, napi_call_threadsafe_function(
addon->tsfn, // js-callback 函數(shù)
NULL, // call_js 的第四個參數(shù)
napi_tsfn_blocking)); // 阻塞模式調(diào)用
// 釋放句柄
catch_err(env, napi_release_threadsafe_function(addon->tsfn, napi_tsfn_release));
}
/** 線程執(zhí)行完成 */
static void work_complete(napi_env env, napi_status status, void* data) {
Addon* addon = (Addon*)data;
// 釋放句柄
catch_err(env, napi_release_threadsafe_function(addon->tsfn, napi_tsfn_release));
// 回收任務(wù)
catch_err(env, napi_delete_async_work(env, addon->work));
addon->work = NULL;
addon->tsfn = NULL;
}
/**
* start_thread 啟動線程
* 關(guān)于 static 關(guān)鍵字用不用都行的,官方的例子有用到 static
* 以我 js 的能力我猜的可能是開多個線程下,可以公用一個函數(shù),節(jié)約內(nèi)存開銷 (歡迎大神來討論 ??)
*/
static napi_value start_thread(napi_env env, napi_callback_info info) {
size_t argc = 1; // js 傳進來的參數(shù)個數(shù)
napi_value js_cb; // js 傳進來的回調(diào)函數(shù)
napi_value work_name; // 給線程起個名字
Addon* addon; // “實例化” 結(jié)構(gòu)體 (個人理解是取出了傳進來的 js-cabllback 地址指針,期待大神來討論 ??)
napi_status sts; // 程序執(zhí)行狀態(tài)
sts = napi_get_cb_info(
env, // 執(zhí)行上下文,可以理解為 js 那個 “事件環(huán)”
info, // 上下文信息
&argc, // 收到參數(shù)的個數(shù)
&js_cb, // 接收 js 參數(shù)
NULL, // 接收 js 的 this 對象
(void**)(&addon) // 取得 js 傳進來的 callback 的指針地址
);
catch_err(env, sts);
// 打醬油的 ^_^
assert(addon->work == NULL && "Only one work item must exist at a time");
// 創(chuàng)建線程名字
catch_err(env, napi_create_string_utf8(env,
"N-API Thread-safe Call from Async Work Item",
NAPI_AUTO_LENGTH,
&work_name));
// 把 js function 變成任意線程都可以執(zhí)行的函數(shù)
// 醬紫我們就可以在開出來的子線程中調(diào)用它咯
sts = napi_create_threadsafe_function(env,
// 其他線程的 js 函數(shù)
// call_js 的第二個參數(shù)
// 也就是我們 addon.start(function) 傳進來的 function
js_cb,
// 可能傳遞給一些異步任務(wù)async_hooks鉤子傳遞初始化數(shù)據(jù) (期待大神討論 ??)
// 個人理解 N-API 中的 async 指的就是多線程任務(wù)
// 一個線程任務(wù),在 N-API 中由 async work 調(diào)用
NULL,
work_name, // 給線程起個名字,給 async_hooks 鉤子提供一個標(biāo)識符
0, // (官網(wǎng)直譯)最大線程隊列數(shù)量,0 代表沒限制
1, // (官網(wǎng)直譯)初始化線程數(shù)量,其中包括主線程
NULL, // (官網(wǎng)直譯)線程之間可以傳遞數(shù)據(jù)(官網(wǎng)直譯)
NULL, // (官網(wǎng)直譯)線程之間可以傳遞函數(shù),函數(shù)注銷時候被調(diào)用
NULL, // (官網(wǎng)直譯)附加給函數(shù)的執(zhí)行上下文,應(yīng)該就是
call_js, // call_js 的第三個參數(shù)
&(addon->tsfn)); // js 傳進來的函數(shù),可以理解為真實的 js 函數(shù)所在內(nèi)存地址 (期待大神討論 ??)
catch_err(env, sts);
// 負(fù)責(zé)執(zhí)行上面創(chuàng)建的函數(shù)
sts = napi_create_async_work(env,
NULL, // 可能傳遞 async_hooks 一些初始化數(shù)據(jù)
work_name, // 給線程起個名字,給 async_hooks 鉤子提供一個標(biāo)識符
execute_work, // 線程執(zhí)行時候執(zhí)行的函數(shù) (與主線程并行執(zhí)行)
work_complete, // 線程執(zhí)行完時候的回調(diào)
addon, // 既 execute_work、work_complete 中的 void* data
&(addon->work)); // 線程句柄
catch_err(env, sts);
// 將線程放到待執(zhí)行隊列中
sts = napi_queue_async_work(env,
// 要執(zhí)行線程的句柄
addon->work);
catch_err(env, sts);
return NULL; // 這個貌似是返回給 js-callback 的返回值
}
napi_value init(napi_env env, napi_value exports) {
// 這里等價于 const obj = new Object();
// 這回知道面向?qū)ο笫钦淼牧税???
// 類的本質(zhì)就是“結(jié)構(gòu)體”演化而來的,new(開辟堆內(nèi)存空間) 關(guān)鍵字是 malloc(申請內(nèi)存空間) 變種過來的
Addon* addon = (Addon*)malloc(sizeof(*addon));
// 等價于 obj.work = null;
addon->work = NULL;
// 個人 js 水平有限,Object 類研究的不深
// 可以說,精通 Object 類的小伙伴可以自己想想咯,反正給對象掛一個屬性、函數(shù)需要的東東,都在這里了
// 相等于 const fun = () => {}, attr = 'Hello';
napi_property_descriptor desc = {
"start", // 屬性名稱
NULL, // -- 沒想明白
start_thread, // 函數(shù)體
NULL, // 屬性 getter
NULL, // 屬性 setter
NULL, // 屬性描述符
napi_default,
addon // (官網(wǎng)直譯)也可以寫 NULL,調(diào)用 getter 時候返回的數(shù)據(jù)
};
// 相當(dāng)于 const obj = { fun, attr };
napi_define_properties(env, exports, 1, &desc); // 將屬性掛載到 exports 上面
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
// 配合另一種導(dǎo)出模塊用的
//static void addon_getting_unloaded(napi_env env, void* data, void* hint) {
// Addon* addon = (Addon*)data;
// assert(addon->work == NULL && "No work in progress at module unload");
// free(addon);
//}
// 另一種導(dǎo)出模塊的宏定義(有空再研究??)
//NAPI_MODULE_INIT() {
// Addon* addon = (Addon*)malloc(sizeof(*addon));
// addon->work = NULL;
//
// napi_property_descriptor desc = {
// "start",
// NULL,
// start_thread,
// NULL,
// NULL,
// NULL,
// napi_default,
// addon
// };
//
// napi_define_properties(env, exports, 1, &desc);
// napi_wrap(env, exports, addon, addon_getting_unloaded, NULL, NULL);
// // return exports; // 可寫可不寫
//}
- javascript
test/.js
const addon = require('../build/Release/thread_cb.node');
let second = 0;
let cb_exucted = false;
addon.start(function() {
cb_exucted = true;
console.log(`Asynchronous callback exectued.`);
});
const t = setInterval(() => {
if (cb_exucted) {
clearTimeout(t);
}
console.log(`After ${++second} seconds.`);
}, 1000);
- 運行
$ node test/thread-cb.js
After 1 seconds.
After 2 seconds.
After 3 seconds.
Asynchronous callback exectued.
After 4 seconds.
OK! 木有任何問題,我們親愛的 js 還是你熟悉的那個非阻塞模式運行 ??
寫在最后:
雖然這是教程的第三篇,但這篇是最難的了,我英語又不好看官方 N-API 文檔好費勁的 ??
其次個人覺得吧現(xiàn)在地表上面的操作系統(tǒng)就windwos
、*nix
兩款了,還都是 C 語言打造的,所以好多時候你再厲害的語言(java,php,python...)底層和 C 及 C 造的操作系統(tǒng)都有著千絲萬縷的聯(lián)系
所以說,雖然個人作為一個前端er,還是很推薦了解下 C 及 C 的產(chǎn)物操作系統(tǒng),其實操作系統(tǒng)可以簡單的理解是用 C 寫的 工具集合 幫你操作音頻、視頻、網(wǎng)絡(luò)、磁盤、合理調(diào)度 CPU、合適開辟內(nèi)存等等 ??