如何正確利用native的方式實現(xiàn)跨線程調(diào)用叫榕?(鴻蒙開發(fā))

簡介

在OpenHarmony應(yīng)用開發(fā)實踐中,經(jīng)常會遇到一些耗時的任務(wù)姊舵,如I/O操作晰绎、域名解析以及復(fù)雜計算等。這些任務(wù)如果直接在主線程中執(zhí)行括丁,將會嚴重阻塞主線程荞下,影響后續(xù)任務(wù)的正常流程,進而導(dǎo)致用戶界面響應(yīng)延遲甚至卡頓躏将。因此锄弱,為了提升代碼性能,通常會將這類耗時任務(wù)放在子線程中執(zhí)行祸憋。
本文將聚焦于如何利用native的方式實現(xiàn)跨線程調(diào)用会宪,即采用線程安全函數(shù)和libuv異步I/O工具庫這兩種策略,來優(yōu)化程序性能并保持流暢的用戶體驗蚯窥。

注意事項

以下將詳細闡述如何運用native方式創(chuàng)建子線程以執(zhí)行耗時任務(wù)掸鹅,并確保與JavaScript的無縫交互塞帐。為此,開發(fā)者可以利用 arkui_napi 倉庫提供的 NAPI(Node-API) 接口來實現(xiàn)跨語言調(diào)用的橋梁巍沙。該NAPI的設(shè)計嚴格遵循Node.js的NAPI規(guī)范葵姥,以便開發(fā)者能夠更輕松地理解和使用。
特別強調(diào)的是句携,JavaScript函數(shù)通常只能在主線程里調(diào)用榔幸。如果native側(cè)通過std::thread或pthread創(chuàng)建了子線程,那么napi_env矮嫉、napi_value以及napi_ref是不能直接在子線程上下文中使用的削咆。為確保正確性,當native端在子線程完成其計算或處理后蠢笋,若需要回調(diào)JavaScript函數(shù)拨齐,必須先通過線程同步機制將結(jié)果傳遞回主線程,然后才能安全地在主線程環(huán)境中調(diào)用JavaScript函數(shù)昨寞。
為解決這一問題瞻惋,以下將提出兩種有效的解決方案。

解決方案

線性安全函數(shù)

napi_threadsafe_function 提供了接口來創(chuàng)建一個可以在多線程間共享并安全使用的函數(shù)對象援岩。通過這個機制歼狼,子線程可以將數(shù)據(jù)傳遞給主線程,主線程接收到數(shù)據(jù)后會調(diào)用JavaScript回調(diào)函數(shù)進行處理窄俏。該接口包含用于創(chuàng)建蹂匹、銷毀線程安全函數(shù)以及在其之間發(fā)送消息和同步數(shù)據(jù)的方法碘菜。使用napi_threadsafe_function的一般步驟包括:

創(chuàng)建線程安全函數(shù): 通過調(diào)用napi_create_threadsafe_function()創(chuàng)建一個線程安全函數(shù)對象凹蜈。在此過程中,需要指定一個JavaScript回調(diào)函數(shù)忍啸,該函數(shù)將在主線程上執(zhí)行仰坦;同時設(shè)定相關(guān)的上下文信息,這個上下文可以在多個線程之間共享计雌,可以隨時通過調(diào)用napi_get_threadsafe_function_context()來獲取悄晃。此外,還可以選擇性地提供一個napi_finalize回調(diào)凿滤,用于在銷毀線程安全函數(shù)時執(zhí)行資源清理操作妈橄。

獲取使用權(quán): 在開始使用線程安全函數(shù)之前,調(diào)用napi_acquire_threadsafe_function()函數(shù)表明線程已準備就緒翁脆,可以開始對該線程安全函數(shù)進行操作眷蚓。

從子線程調(diào)用回調(diào): 在子線程中,通過調(diào)用napi_call_threadsafe_function()來異步觸發(fā)JavaScript回調(diào)函數(shù)反番,并將所需數(shù)據(jù)作為參數(shù)傳遞給該回調(diào)函數(shù)沙热。調(diào)用會被排隊叉钥,并最終在JavaScript主線程上執(zhí)行。

資源清理: 當線程安全函數(shù)不再需要時篙贸,應(yīng)當正確地釋放和清理與其關(guān)聯(lián)的資源投队。通常調(diào)用napi_release_threadsafe_function()函數(shù)來完成的,該函數(shù)會按照預(yù)定的策略處理尚未執(zhí)行完畢的回調(diào)爵川,并最終銷毀線程安全函數(shù)對象敷鸦。

延長生命周期

在JavaScript層面?zhèn)鬟f給native層的函數(shù)引用,其生命周期僅限于它所在的作用域內(nèi)寝贡。若要確保在超出該作用域后仍能繼續(xù)使用這個函數(shù)引用轧膘,需要采取適當?shù)姆椒▉硌娱L其生命周期。
可以通過調(diào)用napi_create_reference為JavaScript對象創(chuàng)建一個引用(reference)兔甘。這樣可以避免對象因垃圾回收機制而被提前釋放谎碍,從而有效地延長它的生命周期。然而洞焙,在創(chuàng)建引用之后蟆淀,務(wù)必牢記要在不再需要該引用時,調(diào)用napi_delete_reference來釋放引用澡匪,以防止內(nèi)存泄漏問題的發(fā)生熔任。
深入理解并妥善管理JavaScript與native接口之間對象的生命周期,對于編寫高效且無內(nèi)存泄漏隱患的代碼至關(guān)重要唁情。建議開發(fā)者進一步研究 生命周期管理 相關(guān)文檔和最佳實踐疑苔,以便更好地掌握。

libuv

libuv 是一個基于事件驅(qū)動的異步I/O庫甸鸟,對于耗時操作惦费,如果直接在libuv的主循環(huán)(event loop)中處理,會阻塞后續(xù)任務(wù)的執(zhí)行抢韭。為解決這個問題薪贫,libuv內(nèi)部維護了一個線程池,用于執(zhí)行一些耗時操作刻恭,并在這些操作完成后瞧省,將回調(diào)函數(shù)添加回主線程的event loop中等待執(zhí)行。

默認情況下鳍贾,libuv提供的線程池包含4個線程作為基本工作單元鞍匾,但最大線程數(shù)可以擴展到128個。通過預(yù)先設(shè)置環(huán)境變量 UV_THREADPOOL_SIZE 的值骑科,可以自定義線程池中的線程數(shù)量橡淑。當線程池初始化時,會創(chuàng)建相應(yīng)數(shù)量的工作線程纵散,并在每個線程內(nèi)部運行一個 uv_queue_work 函數(shù)梳码。

值得注意的是隐圾,libuv 中的線程池是全局共享資源,不論應(yīng)用中有多少個獨立的事件循環(huán)實例掰茶,它們都共用同一個線程池暇藏。這樣的設(shè)計旨在有效利用系統(tǒng)資源,同時避免因頻繁創(chuàng)建和銷毀線程帶來的開銷濒蒋。

uv_queue_work

    uv_queue_work(uv_loop_t* loop,
                  uv_work_t* req,
                  uv_work_cb work_cb,
                  uv_after_work_cb after_work_cb);

初始化一個工作請求盐碱,通過調(diào)用uv_queue_work函數(shù),可以安排指定的任務(wù)沪伙,在與事件循環(huán)(event loop)關(guān)聯(lián)的線程池中的一個線程上執(zhí)行瓮顽。一旦該任務(wù)(即work_cb回調(diào)函數(shù))完成其操作,將在事件循環(huán)線程中調(diào)用另一個回調(diào)函數(shù)after_work_cb围橡。
各參數(shù)的具體意義如下:
loop: 指向事件循環(huán)結(jié)構(gòu)體的指針暖混,所有異步操作都在這個事件循環(huán)上下文中進行管理。
req: 指向uv_work_t結(jié)構(gòu)體的指針翁授,用于傳遞給工作請求和回調(diào)函數(shù)的數(shù)據(jù)拣播。通常開發(fā)者會將自定義數(shù)據(jù)賦值給req->data成員變量以在回調(diào)中使用。
work_cb: 執(zhí)行實際工作的回調(diào)函數(shù)收擦,一些耗時的操作可以在此執(zhí)行贮配,該函數(shù)在線程池的一個線程上運行。
after_work_cb: 工作完成后在事件循環(huán)線程上調(diào)用的回調(diào)函數(shù)塞赂,常用于處理work_cb執(zhí)行結(jié)果或觸發(fā)進一步的JavaScript層面的操作泪勒。
需要注意的是,盡管uv_queue_work方法本身不直接涉及NAPI(Node-API)接口宴猾,但當涉及到與JavaScript線程交互時圆存,特別是從native層向JavaScript層傳遞數(shù)據(jù)并觸發(fā)回調(diào)時,需要正確地管理napi_value對象的生命周期鳍置。這需要合理使用napi_handle_scope和相關(guān)接口辽剧,來確保在JavaScript回調(diào)方法創(chuàng)建的napi_value對象送淆,在整個執(zhí)行過程中保持有效振坚,并在適當?shù)臅r候釋放資源傀蓉,以避免內(nèi)存泄漏問題。

示例代碼

下面的示例分別用線程安全函數(shù)和libuv實現(xiàn)了native的跨線程調(diào)用。該示例在ArkTS端傳入的JavaScript回調(diào)函數(shù)中對變量value進行加10運算帐姻,在native側(cè)開啟了3個子線程執(zhí)行業(yè)務(wù)邏輯,子線程業(yè)務(wù)邏輯完成之后回到主線程執(zhí)行ArkTS端傳入的JavaScript回調(diào)函數(shù)箱蝠,從而完成了對ArkTS端變量value的加30操作煤杀。

1.使用線程安全函數(shù)

ArkTS實現(xiàn)一個JavaScript回調(diào)函數(shù)。
參數(shù)為param谒出,函數(shù)體中對參數(shù)param加10后綁定變量value隅俘,并返回最新的param值邻奠。將回調(diào)函數(shù)作為參數(shù)調(diào)用native側(cè)的ThreadSafeTest接口。

    //  src/main/ets/pages/Index.ets
        
    Button("threadSafeTest")
      .width('40%')
      .fontSize(20)
      .onClick(()=> {
        // native使用線程安全函數(shù)實現(xiàn)跨線程調(diào)用
        entry.ThreadSafeTest((param: number) => {
          param += 10;
          logger.info('ThreadSafeTest js callback value = ', param.toString());
          this.value = param;
          return param;
        }
      )
    }).margin(20)

native主線程中實現(xiàn)一個ThreadSafeTest接口为居。

接口接收到ArkTS傳入的JavaScript回調(diào)函數(shù)后通過napi_create_threadsafe_function創(chuàng)建一個線程安全函數(shù)tsfn碌宴,tsfn會回調(diào)主線程中的ThreadSafeCallJs,然后在ThreadSafeCallJs中調(diào)用ArkTS端傳入的JavaScript回調(diào)函數(shù)蒙畴。

    //  src/main/cpp/hello.cpp

    napi_threadsafe_function tsfn;   // 線程安全函數(shù)
    static int g_cValue;             // 保存value最新的值,作為參數(shù)傳給js回調(diào)函數(shù)
    int g_threadNum = 3;             // 線程數(shù)

    struct CallbackContext {
        napi_env env = nullptr;
        napi_ref callbackRef = nullptr;
        int retData = 0;
    };

    // 安全函數(shù)回調(diào)
    static void ThreadSafeCallJs(napi_env env, napi_value js_cb, void *context, void *data)
    {
        CallbackContext *argContent = (CallbackContext *)data;
        if (argContent != nullptr) {
            OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs start, retData:[%{public}d]", argContent->retData);
            napi_get_reference_value(env, argContent->callbackRef, &js_cb);
        } else {
            OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs argContent is null");
            return;
        }

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, js_cb, &valueType);
        if (valueType != napi_valuetype::napi_function) {
            OH_LOG_ERROR(LOG_APP, "ThreadSafeTest callback param is not function");
            if (argContent != nullptr) {
                napi_delete_reference(env, argContent->callbackRef);
                delete argContent;
                argContent = nullptr;
                OH_LOG_INFO(LOG_APP, "ThreadSafeTest delete argContent");
            }
            return;
        }
        // 將當前value值作為參數(shù)調(diào)用js函數(shù)
        napi_value argv;
        napi_create_int32(env, g_cValue, &argv);
        napi_value result = nullptr;
        napi_call_function(env, nullptr, js_cb, 1, &argv, &result);
        // g_cValue保存調(diào)用js后的返回結(jié)果
        napi_get_value_int32(env, result, &g_cValue);
        OH_LOG_INFO(LOG_APP, "ThreadSafeTest CallJs end, [%{public}d]", g_cValue);
        if (argContent != nullptr) {
            napi_delete_reference(env, argContent->callbackRef);
            delete argContent;
            argContent = nullptr;
            OH_LOG_INFO(LOG_APP, "ThreadSafeTest delete argContent end");
        }
    }
    
    // 使用安全函數(shù)跨線程調(diào)用js函數(shù)
    static napi_value ThreadSafeTest(napi_env env, napi_callback_info info)
    {
        size_t argc = 1;
        napi_value js_cb;
        napi_value workName;
        // 獲取ArkTS 參數(shù)
        napi_get_cb_info(env, info, &argc, &js_cb, nullptr, nullptr);
        // 判斷參數(shù)類型
        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, js_cb, &valueType);
        if (valueType != napi_valuetype::napi_function) {
        OH_LOG_ERROR(LOG_APP, "ThreadSafeTest callback param is not function");
        return nullptr;
        }
        OH_LOG_INFO(LOG_APP, "ThreadSafeTest current value: [%{public}d]", g_cValue);
    
        // 使用安全線程跨線程調(diào)用js 函數(shù)
        napi_create_string_utf8(env, "workItem", NAPI_AUTO_LENGTH, &workName);
        // 創(chuàng)建線程安全函數(shù)
        napi_create_threadsafe_function(env, js_cb, NULL, workName, 0, 1, NULL, NULL, NULL, ThreadSafeCallJs, &tsfn);

在native子線程中調(diào)用線程安全函數(shù)贰镣。
通過std::thread創(chuàng)建子線程,在子線程中通過napi_call_threadsafe_function調(diào)用線程安全函數(shù)tsfn膳凝,把CallbackContext 結(jié)構(gòu)體數(shù)據(jù)作為參數(shù)傳入ThreadSafeCallJs碑隆。這里在子線程中進行了簡單的業(yè)務(wù)處理,開發(fā)者可以根據(jù)自身實際需求進行相應(yīng)的業(yè)務(wù)操作蹬音。

  //   src/main/cpp/hello.cpp

    // 在子線程中調(diào)用線程安全函數(shù)
    for (int i = 0; i < g_threadNum; i++) {
        // 創(chuàng)建回調(diào)參數(shù)
        auto asyncContext = new CallbackContext();
        asyncContext->env = env;
        asyncContext->retData = i;
        napi_create_reference(env, js_cb, 1, &asyncContext->callbackRef);
        std::thread t([asyncContext]() {
            // 處理業(yè)務(wù)邏輯
            OH_LOG_INFO(LOG_APP, "ThreadSafeTest ChildTread start, index:[%{public}d], value: [%{public}d]",
                        asyncContext->retData, g_cValue);
            asyncContext->retData++;
            // 請求線程安全函數(shù)
            napi_acquire_threadsafe_function(tsfn);
            // 調(diào)用線程安全函數(shù)
            napi_call_threadsafe_function(tsfn, asyncContext, napi_tsfn_nonblocking);
            OH_LOG_INFO(LOG_APP, "ThreadSafeTest ChildTread end, index:[%{public}d], value: [%{public}d]",
                        asyncContext->retData, g_cValue);

            /* 以下直接在子線程中調(diào)用js函數(shù),會崩潰
            napi_value result = nullptr;
            napi_value argv;
            napi_create_int32(env,g_cValue, &argv);
            napi_call_function(env, nullptr, js_cb, 1, &argv, &result);
          */
        });
        t.join();
    }
    // 釋放安全線程
    napi_status status = napi_release_threadsafe_function(tsfn, napi_tsfn_release);
    if (status == napi_status::napi_ok) {
        OH_LOG_INFO(LOG_APP, "ThreadSafeTest napi_tsfn_release success.");
    } else {
        OH_LOG_INFO(LOG_APP, "ThreadSafeTest napi_tsfn_release fail !");
    }

2.使用libuv

ArkTS實現(xiàn)一個JavaScript回調(diào)函數(shù)上煤。
參數(shù)為param,函數(shù)體中對參數(shù)param加10后綁定變量value著淆,并返回最新的param值楼入。然后將回調(diào)函數(shù)作為參數(shù)調(diào)用native側(cè)的UvWorkTest接口。

    //  src/main/ets/pages/Index.ets
        
    Button("libuvTest")
      .width('40%')
      .fontSize(20)
      .onClick(()=> {
        // native使用線程安全函數(shù)實現(xiàn)跨線程調(diào)用
        entry.UvWorkTest((param: number) => {
          param += 10;
          logger.info('UvWorkTest js callback value = ', param.toString());
          this.value = param;
          return param;
        }
      )
    }).margin(20)

native主線程中實現(xiàn)一個UvWorkTest接口牧抽。
接口接收到ArkTS傳入的JavaScript回調(diào)函數(shù)后創(chuàng)建子線程嘉熊,在子線程的執(zhí)行函數(shù)CallbackUvWorkTest中創(chuàng)建工作任務(wù)workReq,通過uv_queue_work將工作任務(wù)添加到libuv隊列中扬舒。

  //   src/main/cpp/hello.cpp

    void CallbackUvWorkTest(CallbackContext *context)
    {
        if (context == nullptr) {
            OH_LOG_ERROR(LOG_APP, "UvWorkTest context is nullptr");
            return;
        }
        uv_loop_s *loop = nullptr;
        napi_get_uv_event_loop(context->env, &loop);
        // 創(chuàng)建工作數(shù)據(jù)結(jié)構(gòu),自定義數(shù)據(jù)結(jié)構(gòu)添加在data中
        uv_work_t *workReq = new uv_work_t;
        if (workReq == nullptr) {
            if (context != nullptr) {
                napi_delete_reference(context->env, context->callbackRef);
                delete context;
                OH_LOG_INFO(LOG_APP, "UvWorkTest delete context");
                context = nullptr;
            }
            OH_LOG_ERROR(LOG_APP, "UvWorkTest new uv_work_t fail!");
            return;
        }
        workReq->data = (void *)context;
        // 此打印位于子線程
        OH_LOG_INFO(LOG_APP, "UvWorkTest childThread_1 [%{public}d]", g_cValue);
        // 添加工作任務(wù)到libuv的隊列中
        uv_queue_work(loop, workReq, WorkCallback, AfterWorkCallback);
    }
    
    // 使用uv_work callback 實現(xiàn)跨線程調(diào)用js函數(shù)
    static napi_value UvWorkTest(napi_env env, napi_callback_info info)
    {
        size_t argc = 1;
        napi_value argv[1] = {0};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
    
        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);
        if (valueType != napi_function) {
            OH_LOG_ERROR(LOG_APP, "UvWorkTest param is not function");
            return nullptr;
        }
    
        OH_LOG_INFO(LOG_APP, "UvWorkTest current value:[%{public}d]", g_cValue);
        for (int i = 0; i < g_threadNum; i++) {
            auto asyncContext = new CallbackContext();
            if (asyncContext == nullptr) {
                OH_LOG_ERROR(LOG_APP, "UvWorkTest new asyncContext fail!");
                return nullptr;
            }
            asyncContext->env = env;
            asyncContext->retData = i;
            OH_LOG_INFO(LOG_APP, "UvWorkTest thread begin index:[%{public}d], value:[%{public}d]", i, g_cValue);
            napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
            // using callback function on other thread
            std::thread testThread(CallbackUvWorkTest, asyncContext);
            testThread.detach();
            OH_LOG_INFO(LOG_APP, "UvWorkTest thread end index:[%{public}d], value:[%{public}d]", i, g_cValue);
        }
        return nullptr;
    }

實現(xiàn)work_cb與after_work_cb阐肤。
work_cb位于子線程中,執(zhí)行實際的業(yè)務(wù)邏輯讲坎;after_work_cb位于主線程中孕惜,通過napi_call_function調(diào)用ArkTS端傳入的JavaScript回調(diào)函數(shù)。

    // src/main/cpp/hello.cpp

    void WorkCallback(uv_work_t *workReq)
    {
        // 另外一個子線程,一些耗時操作可以在此進行. 此處不能調(diào)用js函數(shù).
        CallbackContext *context = (CallbackContext *)workReq->data;
        if (context != nullptr) {
            OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack1 childThread_2 [%{public}d]", context->retData);
            context->retData++;
            OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack2 childThread_2 [%{public}d]", context->retData);
        } else {
            OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack3 childThread_2 context is null.");
        }
    }
    
    void AfterWorkCallback(uv_work_t *workReq, int status)
    {
        CallbackContext *context = (CallbackContext *)workReq->data;
        // 主線程執(zhí)行晨炕,可以在此調(diào)用js函數(shù)
        OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack mainThread [%{public}d]", context->retData);
        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(context->env, &scope);
        if (scope == nullptr) {
            if (context != nullptr) {
                napi_delete_reference(context->env, context->callbackRef);
                delete context;
                context = nullptr;
            }
            if (workReq != nullptr) {
                delete workReq;
                workReq = nullptr;
            }
            return;
        }
        napi_value callback = nullptr;
        napi_get_reference_value(context->env, context->callbackRef, &callback);
        napi_value retArg;
        OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack begin [%{public}d]", g_cValue);
        napi_create_int32(context->env, g_cValue, &retArg);
        napi_value ret;
        napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret);
        // 保存js回調(diào)結(jié)果
        napi_get_value_int32(context->env, ret, &g_cValue);
        OH_LOG_INFO(LOG_APP, "UvWorkTest CallBack end [%{public}d]", g_cValue);
    
        napi_close_handle_scope(context->env, scope);
        if (context != nullptr) {
            napi_delete_reference(context->env, context->callbackRef);
            delete context;
            OH_LOG_INFO(LOG_APP, "UvWorkTest delete context");
            context = nullptr;
        }
        if (workReq != nullptr) {
            delete workReq;
            OH_LOG_INFO(LOG_APP, "UvWorkTest delete work");
            workReq = nullptr;
        }
    }

總結(jié)

線程安全函數(shù)和libuv方案都是在子線程的執(zhí)行函數(shù)運行結(jié)束后回到主線程衫画,并將JavaScript回調(diào)函數(shù)push到主線程的event-loop隊列里等待被執(zhí)行。

兩者的差異在于libuv的子線程屬于libuv線程池瓮栗,而線程安全函數(shù)的子線程需要根據(jù)業(yè)務(wù)要求自己創(chuàng)建削罩。另外在libuv中,JavaScript回調(diào)函數(shù)只能在子線程的主函數(shù)執(zhí)行完畢后被動被執(zhí)行费奸;而在線程安全函數(shù)中弥激,JavaScript回調(diào)函數(shù)則可以在任意線程中主動調(diào)用。

寫在最后

如果你覺得這篇內(nèi)容對你還蠻有幫助愿阐,我想邀請你幫我三個小忙

  • 點贊微服,轉(zhuǎn)發(fā),有你們的 『點贊和評論』缨历,才是我創(chuàng)造的動力以蕴。
  • 關(guān)注小編糙麦,同時可以期待后續(xù)文章ing??,不定期分享原創(chuàng)知識丛肮。
  • 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識點喳资,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腾供,隨后出現(xiàn)的幾起案子仆邓,更是在濱河造成了極大的恐慌,老刑警劉巖伴鳖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件节值,死亡現(xiàn)場離奇詭異,居然都是意外死亡榜聂,警方通過查閱死者的電腦和手機搞疗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來须肆,“玉大人匿乃,你說我怎么就攤上這事⊥慊悖” “怎么了幢炸?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拒贱。 經(jīng)常有香客問我宛徊,道長,這世上最難降的妖魔是什么逻澳? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任闸天,我火速辦了婚禮,結(jié)果婚禮上斜做,老公的妹妹穿的比我還像新娘苞氮。我一直安慰自己,他們只是感情好瓤逼,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布笼吟。 她就那樣靜靜地躺著,像睡著了一般抛姑。 火紅的嫁衣襯著肌膚如雪赞厕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天定硝,我揣著相機與錄音,去河邊找鬼毫目。 笑死蔬啡,一個胖子當著我的面吹牛诲侮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播箱蟆,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沟绪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了空猜?” 一聲冷哼從身側(cè)響起绽慈,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辈毯,沒想到半個月后坝疼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡谆沃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年钝凶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唁影。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耕陷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出据沈,到底是詐尸還是另有隱情哟沫,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布锌介,位于F島的核電站南用,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏掏湾。R本人自食惡果不足惜裹虫,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望融击。 院中可真熱鬧筑公,春花似錦、人聲如沸尊浪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拇涤。三九已至捣作,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鹅士,已是汗流浹背券躁。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人也拜。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓以舒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慢哈。 傳聞我的和親對象是個殘疾皇子蔓钟,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容