> 文章列表 > 解决webassembly pthread 子线程调用主线程js问题

解决webassembly pthread 子线程调用主线程js问题

解决webassembly pthread 子线程调用主线程js问题

解决webassembly pthread 子线程调用主线程js问题

背景:

web端项目做了一段时间后,我们需求是加载工程是异步的,主线程会调用wasm方法,wasm内部用pthread创建出来线程,然后在这个线程里边处理任务,处理完成后,需要通知主线程加载完成了,但是这个通知怎么实现,花了一些时间。下边就是之前整理的方案

具体调用逻辑如下图所示:

 

我们知道web端work相关通信是 通过postmessage,onmessage实现的,接下来尝试各种方案

方案一:

Webassembly 我们用c或者c++创建的线程,编译器最终把它转成work,也就是说我们能不能通过work 之间通信解决这个问题呢,我想是可以的。但是十分不方便,因为编译在编译后生成浇水js代码,以及创建线程需要work的浇水代码,我们要改动浇水里边的通信方式, 在次编译后又给覆盖了,极其不方便。

 

从上图也可以看出来,改好后,重新编译代码又给覆盖了。

方案二

js MessageChannel   也可以实现work之间通信。

Instance properties

MessageChannel.port1 Read only

Returns port1 of the channel.

MessageChannel.port2 Read only

Returns port2 of the channel.

具体用法网上有很多,work 持有MessageChange port2, 主线程池游port1,两者可以通过port的postMessage与onMessage进行通信,这种方法在js层面比较方便,但是我们这个要求是wasm里边的work,

主线程怎么把port给传到子线程work中,都比较费力,显然也不是好的方案

方案三

主线程轮训的方式,也就是我们主线程调用LoadProject后,我们返回一个uniqueid, 主线程设置setInterval,开启个定时任务,每隔一定时间查询下uniqueid 代码的任务是否完成,原理是uniqueid关联一个future,可以通过future去实现任务是否完成的查询。

很显然这种方式是可以的,但是需要额外的浪费主线程的一点点性能,

方案四

有没有直接通过调用一个c方法直接把方法抛到主线程去执行的呢,这也是我所期望的。为了实现这个目标我约的emscripten 里边的源码,找到了一些蛛丝马迹,觉得应该是有接口直接调用的。

在test_pthread_proxying.c这个文件中看到了,emscripten_proxy_async 这个方法,接着我就去看下这个方法对应的实现,

C++
int emscripten_proxy_async(em_proxying_queue* q,
                           pthread_t target_thread,
                           void (*func)(void*),
                           void* arg) {
  assert(q != NULL);
      pthread_mutex_lock(&q->mutex);
      em_task_queue* tasks = get_or_add_tasks_for_thread(q, target_thread);
      pthread_mutex_unlock(&q->mutex);
      if (tasks == NULL) {
        return 0;
      }
      pthread_mutex_lock(&tasks->mutex);
      int enqueued = em_task_queue_enqueue(tasks, (task){func, arg});
      pthread_mutex_unlock(&tasks->mutex);
      if (!enqueued) {
        return 0;
      }
  return 1;
}
void em_task_queue_execute(em_task_queue* queue) {
      queue->processing = 1;
      pthread_mutex_lock(&queue->mutex);
      while (!em_task_queue_is_empty(queue)) {
        task t = em_task_queue_dequeue(queue);
        // Unlock while the task is running to allow more work to be queued in
        // parallel.
        pthread_mutex_unlock(&queue->mutex);
        t.func(t.arg);
        pthread_mutex_lock(&queue->mutex);
      }
      pthread_mutex_unlock(&queue->mutex);
      queue->processing = 0;
}

通过上边源码也可以看的出来target_thread,是目标线程的id,可以通过pthread_self()获取, func与args需要执行函数,与args参数,

首先会把根据target_thread 把所在线程的tasks(任务队列取出来),然后把我们的func加进去, 所在线程会调用emscripten_proxy_execute_queue 来执行任务队列,因此这种方式满足了我们的需求。