利用native的方式實現(xiàn)跨線程調用

簡介

在OpenHarmony應用開發(fā)實踐中,經常會遇到一些耗時的任務充包,如I/O操作副签、域名解析以及復雜計算等。這些任務如果直接在主線程中執(zhí)行基矮,將會嚴重阻塞主線程继薛,影響后續(xù)任務的正常流程,進而導致用戶界面響應延遲甚至卡頓愈捅。因此遏考,為了提升代碼性能,通常會將這類耗時任務放在子線程中執(zhí)行蓝谨。
本文將聚焦于如何利用native的方式實現(xiàn)跨線程調用灌具,即采用線程安全函數(shù)和libuv異步I/O工具庫這兩種策略,來優(yōu)化程序性能并保持流暢的用戶體驗譬巫。

注意事項

以下將詳細闡述如何運用native方式創(chuàng)建子線程以執(zhí)行耗時任務咖楣,并確保與JavaScript的無縫交互。為此芦昔,開發(fā)者可以利用arkui_napi倉庫提供的NAPI(Node-API)接口來實現(xiàn)跨語言調用的橋梁诱贿。該NAPI的設計嚴格遵循Node.js的NAPI規(guī)范,以便開發(fā)者能夠更輕松地理解和使用咕缎。
特別強調的是珠十,JavaScript函數(shù)通常只能在主線程里調用。如果native側通過std::thread或pthread創(chuàng)建了子線程凭豪,那么napi_env地梨、napi_value以及napi_ref是不能直接在子線程上下文中使用的携悯。為確保正確性计贰,當native端在子線程完成其計算或處理后聂抢,若需要回調JavaScript函數(shù)拯钻,必須先通過線程同步機制將結果傳遞回主線程,然后才能安全地在主線程環(huán)境中調用JavaScript函數(shù)撰豺。
為解決這一問題粪般,以下將提出兩種有效的解決方案。

解決方案

線性安全函數(shù)

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

創(chuàng)建線程安全函數(shù): 通過調用napi_create_threadsafe_function()創(chuàng)建一個線程安全函數(shù)對象梭纹。在此過程中躲惰,需要指定一個JavaScript回調函數(shù),該函數(shù)將在主線程上執(zhí)行变抽;同時設定相關的上下文信息础拨,這個上下文可以在多個線程之間共享,可以隨時通過調用napi_get_threadsafe_function_context()來獲取绍载。此外诡宗,還可以選擇性地提供一個napi_finalize回調,用于在銷毀線程安全函數(shù)時執(zhí)行資源清理操作击儡。

獲取使用權: 在開始使用線程安全函數(shù)之前塔沃,調用napi_acquire_threadsafe_function()函數(shù)表明線程已準備就緒,可以開始對該線程安全函數(shù)進行操作阳谍。

從子線程調用回調: 在子線程中蛀柴,通過調用napi_call_threadsafe_function()來異步觸發(fā)JavaScript回調函數(shù),并將所需數(shù)據(jù)作為參數(shù)傳遞給該回調函數(shù)矫夯。調用會被排隊鸽疾,并最終在JavaScript主線程上執(zhí)行。

資源清理: 當線程安全函數(shù)不再需要時训貌,應當正確地釋放和清理與其關聯(lián)的資源制肮。通常調用napi_release_threadsafe_function()函數(shù)來完成的,該函數(shù)會按照預定的策略處理尚未執(zhí)行完畢的回調递沪,并最終銷毀線程安全函數(shù)對象豺鼻。

延長生命周期

在JavaScript層面?zhèn)鬟f給native層的函數(shù)引用,其生命周期僅限于它所在的作用域內区拳。若要確保在超出該作用域后仍能繼續(xù)使用這個函數(shù)引用拘领,需要采取適當?shù)姆椒▉硌娱L其生命周期。
可以通過調用napi_create_reference為JavaScript對象創(chuàng)建一個引用(reference)樱调。這樣可以避免對象因垃圾回收機制而被提前釋放约素,從而有效地延長它的生命周期。然而笆凌,在創(chuàng)建引用之后圣猎,務必牢記要在不再需要該引用時,調用napi_delete_reference來釋放引用乞而,以防止內存泄漏問題的發(fā)生送悔。
深入理解并妥善管理JavaScript與native接口之間對象的生命周期,對于編寫高效且無內存泄漏隱患的代碼至關重要爪模。建議開發(fā)者進一步研究生命周期管理相關文檔和最佳實踐欠啤,以便更好地掌握。

libuv

libuv是一個基于事件驅動的異步I/O庫屋灌,對于耗時操作洁段,如果直接在libuv的主循環(huán)(event loop)中處理,會阻塞后續(xù)任務的執(zhí)行共郭。為解決這個問題祠丝,libuv內部維護了一個線程池,用于執(zhí)行一些耗時操作除嘹,并在這些操作完成后写半,將回調函數(shù)添加回主線程的event loop中等待執(zhí)行。
默認情況下尉咕,libuv提供的線程池包含4個線程作為基本工作單元叠蝇,但最大線程數(shù)可以擴展到128個。通過預先設置環(huán)境變量 UV_THREADPOOL_SIZE 的值年缎,可以自定義線程池中的線程數(shù)量悔捶。當線程池初始化時,會創(chuàng)建相應數(shù)量的工作線程晦款,并在每個線程內部運行一個 uv_queue_work 函數(shù)炎功。
值得注意的是,libuv 中的線程池是全局共享資源缓溅,不論應用中有多少個獨立的事件循環(huán)實例蛇损,它們都共用同一個線程池。這樣的設計旨在有效利用系統(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);

初始化一個工作請求,通過調用uv_queue_work函數(shù)袜匿,可以安排指定的任務更啄,在與事件循環(huán)(event loop)關聯(lián)的線程池中的一個線程上執(zhí)行。一旦該任務(即work_cb回調函數(shù))完成其操作居灯,將在事件循環(huán)線程中調用另一個回調函數(shù)after_work_cb祭务。
各參數(shù)的具體意義如下:
loop: 指向事件循環(huán)結構體的指針内狗,所有異步操作都在這個事件循環(huán)上下文中進行管理。
req: 指向uv_work_t結構體的指針义锥,用于傳遞給工作請求和回調函數(shù)的數(shù)據(jù)柳沙。通常開發(fā)者會將自定義數(shù)據(jù)賦值給req->data成員變量以在回調中使用。
work_cb: 執(zhí)行實際工作的回調函數(shù)拌倍,一些耗時的操作可以在此執(zhí)行赂鲤,該函數(shù)在線程池的一個線程上運行。
after_work_cb: 工作完成后在事件循環(huán)線程上調用的回調函數(shù)柱恤,常用于處理work_cb執(zhí)行結果或觸發(fā)進一步的JavaScript層面的操作数初。
需要注意的是,盡管uv_queue_work方法本身不直接涉及NAPI(Node-API)接口梗顺,但當涉及到與JavaScript線程交互時泡孩,特別是從native層向JavaScript層傳遞數(shù)據(jù)并觸發(fā)回調時,需要正確地管理napi_value對象的生命周期荚守。這需要合理使用napi_handle_scope和相關接口珍德,來確保在JavaScript回調方法創(chuàng)建的napi_value對象,在整個執(zhí)行過程中保持有效矗漾,并在適當?shù)臅r候釋放資源锈候,以避免內存泄漏問題。

示例代碼

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

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

ArkTS實現(xiàn)一個JavaScript回調函數(shù)。
參數(shù)為param蛔垢,函數(shù)體中對參數(shù)param加10后綁定變量value击孩,并返回最新的param值。將回調函數(shù)作為參數(shù)調用native側的ThreadSafeTest接口鹏漆。

 //  src/main/ets/pages/Index.ets
     
 Button("threadSafeTest")
   .width('40%')
   .fontSize(20)
   .onClick(()=> {
     // native使用線程安全函數(shù)實現(xiàn)跨線程調用
     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回調函數(shù)后通過napi_create_threadsafe_function創(chuàng)建一個線程安全函數(shù)tsfn,tsfn會回調主線程中的ThreadSafeCallJs艺玲,然后在ThreadSafeCallJs中調用ArkTS端傳入的JavaScript回調函數(shù)括蝠。

  //  src/main/cpp/hello.cpp

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

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

  // 安全函數(shù)回調
  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ù)調用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保存調用js后的返回結果
      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ù)跨線程調用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);
  
      // 使用安全線程跨線程調用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子線程中調用線程安全函數(shù)。
通過std::thread創(chuàng)建子線程饭聚,在子線程中通過napi_call_threadsafe_function調用線程安全函數(shù)tsfn忌警,把CallbackContext 結構體數(shù)據(jù)作為參數(shù)傳入ThreadSafeCallJs。這里在子線程中進行了簡單的業(yè)務處理秒梳,開發(fā)者可以根據(jù)自身實際需求進行相應的業(yè)務操作法绵。

//   src/main/cpp/hello.cpp

  // 在子線程中調用線程安全函數(shù)
  for (int i = 0; i < g_threadNum; i++) {
      // 創(chuàng)建回調參數(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è)務邏輯
          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);
          // 調用線程安全函數(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);

          /* 以下直接在子線程中調用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回調函數(shù)箕速。
參數(shù)為param,函數(shù)體中對參數(shù)param加10后綁定變量value礼烈,并返回最新的param值弧满。然后將回調函數(shù)作為參數(shù)調用native側的UvWorkTest接口婆跑。

 //  src/main/ets/pages/Index.ets
     
 Button("libuvTest")
   .width('40%')
   .fontSize(20)
   .onClick(()=> {
     // native使用線程安全函數(shù)實現(xiàn)跨線程調用
     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回調函數(shù)后創(chuàng)建子線程,在子線程的執(zhí)行函數(shù)CallbackUvWorkTest中創(chuàng)建工作任務workReq滑进,通過uv_queue_work將工作任務添加到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ù)結構,自定義數(shù)據(jù)結構添加在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);
      // 添加工作任務到libuv的隊列中
      uv_queue_work(loop, workReq, WorkCallback, AfterWorkCallback);
  }
  
  // 使用uv_work callback 實現(xiàn)跨線程調用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è)務邏輯阴汇;after_work_cb位于主線程中,通過napi_call_function調用ArkTS端傳入的JavaScript回調函數(shù)节槐。

  // src/main/cpp/hello.cpp

  void WorkCallback(uv_work_t *workReq)
  {
      // 另外一個子線程,一些耗時操作可以在此進行. 此處不能調用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í)行搀庶,可以在此調用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回調結果
      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;
      }
  }

總結

線程安全函數(shù)和libuv方案都是在子線程的執(zhí)行函數(shù)運行結束后回到主線程,并將JavaScript回調函數(shù)push到主線程的event-loop隊列里等待被執(zhí)行铜异。
兩者的差異在于libuv的子線程屬于libuv線程池哥倔,而線程安全函數(shù)的子線程需要根據(jù)業(yè)務要求自己創(chuàng)建。另外在libuv中揍庄,JavaScript回調函數(shù)只能在子線程的主函數(shù)執(zhí)行完畢后被動被執(zhí)行咆蒿;而在線程安全函數(shù)中,JavaScript回調函數(shù)則可以在任意線程中主動調用蚂子。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末沃测,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子食茎,更是在濱河造成了極大的恐慌蒂破,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件别渔,死亡現(xiàn)場離奇詭異附迷,居然都是意外死亡,警方通過查閱死者的電腦和手機钠糊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門挟秤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抄伍,你說我怎么就攤上這事艘刚。” “怎么了截珍?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵攀甚,是天一觀的道長箩朴。 經常有香客問我,道長秋度,這世上最難降的妖魔是什么炸庞? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮荚斯,結果婚禮上埠居,老公的妹妹穿的比我還像新娘。我一直安慰自己事期,他們只是感情好滥壕,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兽泣,像睡著了一般绎橘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唠倦,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天称鳞,我揣著相機與錄音,去河邊找鬼稠鼻。 笑死冈止,一個胖子當著我的面吹牛,可吹牛的內容都是我干的枷餐。 我是一名探鬼主播靶瘸,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毛肋!你這毒婦竟也來了怨咪?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤润匙,失蹤者是張志新(化名)和其女友劉穎诗眨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孕讳,經...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡匠楚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了厂财。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芋簿。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖璃饱,靈堂內的尸體忽然破棺而出与斤,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布撩穿,位于F島的核電站磷支,受9級特大地震影響,放射性物質發(fā)生泄漏食寡。R本人自食惡果不足惜雾狈,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抵皱。 院中可真熱鬧善榛,春花似錦、人聲如沸叨叙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擂错。三九已至,卻和暖如春樱蛤,著一層夾襖步出監(jiān)牢的瞬間钮呀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工昨凡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留爽醋,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓便脊,卻偏偏與公主長得像蚂四,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哪痰,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內容