【问题标题】:javascript - achieve Multithreading behaviorjavascript - 实现多线程行为
【发布时间】:2014-01-30 17:39:47
【问题描述】:

简介:

我正在构建一个网站,其主要功能之一是白板。您可以与其他用户打开会话,然后您在板上绘制的任何内容他都会看到,反之亦然。

使用 HTML 和 javascript 实现,点对点。

问题:

现在有两件事可以触发在您的板上绘制 - 您绘制了一些东西,或者您在会话中的合作伙伴绘制了一些东西。 (两者都会触发绘图事件)。

当你画东西时问题就开始了,同时你的伙伴也画了。 (图是混的,我的输入和小伙伴收到的混的,不能并行处理两个图输入)

解决方案:(?)

多线程可以解决它。在单独的线程中处理合作伙伴的绘图。但是,JavaScript 不是多线程的。 我阅读了有关使用'worker'(HTML 5)的选项,但如果我理解正确,它在处理主线程中的变量方面非常有限,并且无法影响 HTML(更改屏幕上看到的内容)。

有什么想法吗?

【问题讨论】:

    标签: javascript html multithreading


    【解决方案1】:

    多线程无法为您解决这个问题。如前所述,Web Worker 是有限的,并且在这种情况下不会增加任何好处,因为这需要访问 DOM 等。(您可以使用它们来处理使用可转移对象的像素,即类型化数组,但是您必须在处理后将它们通过管道传回您的主 JS 线程,这样您才能走得更远)。

    不过好消息,您可以同时绘制两幅不同的画。我假设您使用画布来实现这一点。

    只需创建两个重叠的画布,并将您的本地鼠标/触摸移动提供给其中一个画布,同时通过您的网络套接字将数据提供给另一个画布。

    另一种方法是以原子方式绘制每个片段 - 对于每次鼠标移动和通过 Web 套接字接收的表示移动的每个数据(您可能需要在此处迭代),例如:

    ctx.beginPath();                /// reset path for this segment
    ctx.moveTo(oldX, oldY);         /// of current client/user
    ctx.lineTo(x, y);               /// current position
    ctx.strokeStyle = currentColor; /// the one drawing this line
    ctx.stroke();
    

    (oldX/Y 第一次会被鼠标按下事件初始化)。

    以分段方式执行此操作可以让两者同时绘制,尽管它对性能的要求更高。如果线条的顺序不重要,我个人会选择分层画布解决方案。由于您的解决方案将使用事件,因此它将是异步的,并且会处理一个在这里应该可以正常工作的事件队列。

    只需设置一些数组来保存每个用户的数据(旧位置、样式等),这样您就可以为当前用户/客户端使用 ID/索引。

    一些伪代码对分段方法进行简单概述:

    var oldX = [],
        oldY = [],
        currentColor = [], ...;
    
    ...set up clients, arrays, mousedown/up etc.
    
    canvas.onmousemove = function(e) {
        var pos = getMousePos ...
        if (isDrawing) drawSegment(0, pos.x, pos.y);  /// f.ex. client 0 = local
    }
    
    function handleSocketData() {
        ... get x/y from data stream
        drawSegment(1, x, y);          /// f.ex. client 1 = web socket
    }
    
    function drawSegment(client, x, y) {
        ctx.beginPath();
        ctx.moveTo(oldX[client], oldY[client]);
        ctx.lineTo(x, y);
        ctx.strokeStyle = currentColor[client];
        ctx.stroke();
    
        oldX[client] = x;
        oldY[client] = y;
    }
    

    这当然是简化的。您需要将每个点存储在具有与绘制时相同的状态详细信息的点数组中,以便能够在清除后重新绘制画布,处理套接字上的多个段等等,但我认为它给人的印象是如何实现该方法的核心原理。

    希望这会有所帮助!

    【讨论】:

    • 确实,画布上的(未同步的)共享状态似乎是这里的真正问题。要么分离这些状态(使用多个画布),要么删除状态(每个段使用原子操作)。
    • 我不确定分层画布将如何提供帮助 - 尽管它一种有趣的方法,可能对其他任务有用,例如“切换图层”。 (也就是说,切换画布和线条颜色有什么区别?在这两种情况下,除非每个用户没有适当地重置线条开始,否则没有状态。)
    • @user2864740 它有助于您绘制连续线条,例如,这对于共享画布是不可能的,因为只有一条路径可以包含所有(f.ex)lineTo 的累积结果在数据之间纵横交错的绘图中,没有分色等等。因此,要么是为每个线段重置路径的分段方法,要么是一个单独的(实际上是 path 而不是画布本身)解决方案。
    • @user2864740 如果为每个演员分配了一个单独的画布,他们可以在该画布中存储状态(例如笔位置、当前颜色和路径),而不会与其他演员发生冲突。
    • @Ken 关于保持相同路径的好点 - 尽管我不知道使用 Canvas [必须光栅化以显示路径] 是否有优势像 SVG。状态维护(以及使用 moveTo 恢复)对于每个玩家来说都是微不足道的。
    【解决方案2】:

    我认为在这种情况下不需要多个线程。您可以在单线程中完成所有绘图。假设您有一个在两点之间画线的函数。您可以在本地绘制事件(当用户自己绘制时)以及接收到合作伙伴数据时调用此函数。您甚至可以在短暂延迟后为伙伴绘制动画。

    【讨论】:

      【解决方案3】:

      在这种情况下,“竞争条件”与线程无关或通过线程解决 - 实际上它只会让线程变得更加复杂。

      相反,序列化计划/顺序需要在实现绘制哪些线时定义和遵循,以及何时 - 例如谁的台词“在上面”?它是按段还是按路径?下笔(第一个)或上笔(最后一个)的优先级?这可以在 JavaScript 使用的异步模型的上下文中简单地处理。

      最简单的方法是根据客户收到线条的顺序来绘制线条;如果需要一致的视图,则算法/协议需要引入同步,例如共享计数器或“时间戳”,可能由服务器在接收更新时注入。

      可以在对象中轻松维护不同对等点的状态(例如键控到对等点的映射),并且不难设置或维护。


      Web Workers 是 [浏览器] JavaScript 对线程的最接近的东西。

      Web Workers 更类似于 BackgroundWorkers,因为它经常在 UI 框架中使用,例如 Java 中的 Swing 或 .NET WebForms。也就是说,后台任务——无论是 Web Worker 还是 Background Worker——都不允许修改 UI/HTML,但必须触发某种形式的回调,然后在“正确的线程”上进行处理。

      在许多情况下,这实际上比无限制线程和跨线程访问好得多:无需担心一致性或原子性!

      【讨论】:

      • 嗯.. 序列化意味着放弃在屏幕上同时绘制两件事的梦想,而是使用队列。
      • @mosh 如果你在同一个异步事件回调中绘制 N 个东西(比如线条),那么它们将在 same UI 更新中可见。但是,您必须决定绘制 哪些 行,以及它的顺序 - that 是序列化顺序/时间表。使用线程根本没有解决这个概念(使用线程只会使其更难,因为必须添加同步,而不需要为可预测的结果定义序列化顺序)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-23
      • 2021-08-01
      • 2011-09-28
      • 1970-01-01
      • 1970-01-01
      • 2021-12-26
      • 1970-01-01
      相关资源
      最近更新 更多