nodejs创建线程问题

44 篇文章 3 订阅
13 篇文章 0 订阅

我们知道在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。可以捕获一下参数错误的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值