nodejs创建线程问题

时间:2022-07-23
本文章向大家介绍nodejs创建线程问题,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

我们知道在nodejs中可以使用new Worker创建线程。今天有个同学恰好问到,怎么判断创建线程成功,这也是最近开发线程池的时候遇到的问题。nodejs文档里也没有提到如何捕获创建失败这种情况。所以只能通过源码去找答案。不过坏消息是,我们无法捕获这个这个错误。下面看一下源码。我们直接从c++层开始分析。 当我们调用new Worker的时候,最后会调用c++的StartThread函数(node_worker.cc)创建一个线程。

CHECK_EQ(uv_thread_create_ex(&w->tid_, &thread_options, [](void* arg) {
    // ...
  }, static_cast<void*>(w)), 0);

我们看uv_thread_create_ex的逻辑

int uv_thread_create_ex(uv_thread_t* tid,
                        const uv_thread_options_t* params,
                        void (*entry)(void *arg),
                        void *arg) {
  // 忽略部分代码
  err = pthread_create(tid, attr, f.out, arg);
  return UV__ERR(err);
}

接着我们看一下pthread_create的返回值定义

On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.

所以,如果uv_thread_create_ex返回非0,即pthread_create返回非0。表示报错。我们回头看一下返回非0时,c++的处理。我们对c++层的CHECK_EQ(uv_thread_create_ex(…), 0)进行宏展开。

#define CHECK_EQ(a, b) CHECK((a) == (b))

#define CHECK(expr)                                                           
  do {                                                                        
    if (UNLIKELY(!(expr))) {                                                  
      ERROR_AND_ABORT(expr);                                                  
    }                                                                         
  } while (0)

#define UNLIKELY(expr) expr

通过一些列展开,最后变成

  do {                                                                        
    if (!(返回值 == 0)) {                                                  
      ERROR_AND_ABORT(expr);                                                  
    }                                                                         
  } while (0)

因为创建线程时返回非0,所以这里是true。我们继续看ERROR_AND_ABORT

#define ERROR_AND_ABORT(expr)                                                 
  do {                                                                
    static const node::AssertionInfo args = {                                 
      __FILE__ ":" STRINGIFY(__LINE__), #expr, PRETTY_FUNCTION_NAME           
    };                                                                        
    node::Assert(args);                                                       
  } while (0)

拼接错误信息,然后执行node::Assert(args);

[[noreturn]] void Assert(const AssertionInfo& info) {
  char name[1024];
  GetHumanReadableProcessName(&name);

  fprintf(stderr,
          "%s: %s:%s%s Assertion `%s' failed.n",
          name,
          info.file_line,
          info.function,
          *info.function ? ":" : "",
          info.message);
  fflush(stderr);

  Abort();
}

重点是Abort,

[[noreturn]] void Abort() {
  DumpBacktrace(stderr);
  fflush(stderr);
  ABORT_NO_BACKTRACE();
}

继续看ABORT_NO_BACKTRACE

#ifdef _WIN32
#define ABORT_NO_BACKTRACE() _exit(134)
#else
#define ABORT_NO_BACKTRACE() abort()
#endif

所以最终调用的是_exit或abort退出或者终止进程。我们讨论linux下的情况。我们看abort函数的说明

The abort() function first unblocks the SIGABRT signal, and then raises that signal for the calling process (as though raise(3) was called). This results in the abnormal termination of the process unless the SIGABRT signal is caught and the signal handler does not return (see longjmp(3)). If the SIGABRT signal is ignored, or caught by a handler that returns, the abort() function will still terminate the process. It does this by restoring the default disposition for SIGABRT and then raising the signal for a second time.

abort函数会给进程发送SIGABRT信号,我们可以注册函数处理这个信号,不过我们还是无法阻止进程的退出,因为他执行完我们的处理函数后,会把处理函数注册为系统的默认的,然后再次发送SIGABRT信号,而默认的行为就是终止进程。我们来个测试。

const { Worker, threadId } = require('worker_threads');
for (let i = 0; i < 1000; i++) {
    const worker = new Worker('var a = 1;', { eval: true });
}

我们创建1000个线程。结果

总结:在nodejs创建过多的线程可能会导致进程终止。而我们无法阻止这个行为。所以在nodejs里使用多线程的时候,我们需要注意的就是不要开启过多的线程,而在创建线程的时候,我们也不需要关注是否成功,因为只要进程不挂掉,那就是成功。对于业务错误我们可以注册error事件处理,在new Worker的时候,我们可以加try catch。可以捕获一下参数错误的情况。