• libuv里的几个缺陷


    libuv是node.js使用的基础库,主要包括主循环,文件和网络接口。虽然libuv是为node.js而生的,但它本身是一个独立的库,加上使用简单方便,所以在node.js之外也有不少人使用。最近整合libuv到V8里时发现几个问题:

    1.uv_fs相关函数无法传回调函数需要的上下文信息(如read的buffer),只能通过全局变量来保存数据(官方例子都是用的全局变量)。uv_fs相关函数都可以提供一个回调函数,如果回调函数不为空,当前调用自动变成一个异步调用,在操作完成时调用提供的回调函数。一般来说,回调函数都需要一个变量来作为它的上下文(提供参数或保存结果),文件操作相关函数的上下文是uv_fs_t结构,但里面无法保存调用者提供的额外信息。

    uv_fs_t open_req;
    uv_fs_t read_req;
    uv_fs_t write_req;
    
    static char buffer[1024];
    
    static uv_buf_t iov;
    ...
    void on_read(uv_fs_t *req) {
        if (req->result < 0) {
            fprintf(stderr, "Read error: %s
    ", uv_strerror(req->result));
        }
        else if (req->result == 0) {
            uv_fs_t close_req;
            // synchronous
            uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL);
        }
        else if (req->result > 0) {
            iov.len = req->result;
            uv_fs_write(uv_default_loop(), &write_req, 1, &iov, 1, -1, on_write);
        }
    }
    
    void on_open(uv_fs_t *req) {
        // The request passed to the callback is the same as the one the call setup
        // function was passed.
        assert(req == &open_req);
        if (req->result >= 0) {
            iov = uv_buf_init(buffer, sizeof(buffer));
            uv_fs_read(uv_default_loop(), &read_req, req->result,
                       &iov, 1, -1, on_read);
        }
        else {
            fprintf(stderr, "error opening file: %s
    ", uv_strerror((int)req->result));
        }
    }
    ...
    uv_fs_open(uv_default_loop(), &open_req, argv[1], O_RDONLY, 0, on_open);
    

    我开始以为data成员是保存用户数据的地方,通过data把buffer传过去,但是data总是被清空了,看里面的代码才知道data是fs内部的使用的。上面官方例子都是通过全局变量传过去的,真是太变态了!

    2.其它线程不能访问缺省主循环。最近让cantk-runtime-v8里支持Touch/Key事件遇到这个问题:Touch/Key事件是UI线程里的,而V8是在GLSurfaceView的Render线程里的,直接通过JNI调用V8里的JS会导致程序崩溃,所以我想通过uv_idle来串行化,但结果是程序仍然崩溃。记得glib loop里idle是允许多线程访问的,我在设计FTK的主循环时也通过一个pipe来串行化多线程访问主循环的,呵呵,我以为所有的loop都应该支持多线程,一看代码才知道,它并没有加锁也没有用pipe来串行化:

      int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) {           
        if (uv__is_active(handle)) return 0;                                      
        if (cb == NULL) return -EINVAL;                                           
        QUEUE_INSERT_HEAD(&handle->loop->name##_handles, &handle->queue);         
        handle->name##_cb = cb;                                                   
        uv__handle_start(handle);                                                 
        return 0;                                                                 
      }    

    3.uv_async无法传递数据。用uv_idle不行,我决定用uv_async。这次倒是不崩溃了,事件好像也收到了,但游戏里的反应却有些怪异,仔细分析LOG信息,发现touchmove和touchend收到了,但是没有收到touchstart。明明uv_async_send都执行了,为什么主循环却没有处理这个事件呢?继续看代码:

    void uv__async_send(struct uv__async* wa) {
      const void* buf;
      ssize_t len;
      int fd;
      int r;
    
      buf = "";
      len = 1;
      fd = wa->wfd;
    
    #if defined(__linux__)
      if (fd == -1) {
        static const uint64_t val = 1;
        buf = &val;
        len = sizeof(val);
        fd = wa->io_watcher.fd;  /* eventfd */
      }
    #endif
    
      do
        r = write(fd, buf, len);
      while (r == -1 && errno == EINTR);
    
      if (r == len)
        return;
    
      if (r == -1)
        if (errno == EAGAIN || errno == EWOULDBLOCK)
          return;
      abort();
    }
    

    async_send是同步执行的,从上面的代码看不出什么问题,再看接受部分:

    static void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
      struct uv__async* wa;
      char buf[1024];
      unsigned n;
      ssize_t r;
    
      n = 0;
      for (;;) {
        r = read(w->fd, buf, sizeof(buf));
    
        if (r > 0)
          n += r;
    
        if (r == sizeof(buf))
          continue;
    
        if (r != -1)
          break;
    
        if (errno == EAGAIN || errno == EWOULDBLOCK)
          break;
    
        if (errno == EINTR)
          continue;
    
        abort();
      }
    
      wa = container_of(w, struct uv__async, io_watcher);
    
    #if defined(__linux__)
      if (wa->wfd == -1) {
        uint64_t val;
        assert(n == sizeof(val));
        memcpy(&val, buf, sizeof(val));  /* Avoid alignment issues. */
        wa->cb(loop, wa, val);
        return;
      }
    #endif
    
      wa->cb(loop, wa, n);
    }
    

    全部数据读取完了,只是在最后调了一次回调。更郁闷的是我对async的理解是错的:uv_async_t里的data成员并不能用来传递数据,它是在两个线程中无保护的情况下共享的。下面的代码是官方提供的示例,这种方法在低速通信时没问题,速度一快后面的数据自动覆盖前面的数据,所以touchstart被touchmove覆盖而丢失。

    void fake_download(uv_work_t *req) {
        int size = *((int*) req->data);
        int downloaded = 0;
        double percentage;
        while (downloaded < size) {
            percentage = downloaded*100.0/size;
            async.data = (void*) &percentage;
            uv_async_send(&async);
    
            sleep(1);
            downloaded += (200+random())%1000; // can only download max 1000bytes/sec,
                                               // but at least a 200;
        }
    }

    libuv以上这些问题在node.js恰恰不是问题,但独立使用libuv时一定要小心了。

  • 相关阅读:
    Java实现 LeetCode 637 二叉树的层平均值(遍历树)
    Java实现 LeetCode 637 二叉树的层平均值(遍历树)
    PHP xml_set_object() 函数
    PHP xml_set_notation_decl_handler() 函数
    PHP xml_set_external_entity_ref_handler() 函数
    PHP xml_set_element_handler() 函数
    PHP xml_set_default_handler() 函数
    最小直列大小 | min-inline-size (Logical Properties)
    最小宽度 | @viewport.min-width (Device Adaptation)
    最小块大小 | min-block-size (Logical Properties)
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167335.html
Copyright © 2020-2023  润新知