【问题标题】:Boost.Asio: wrapping with different `strand`s several timesBoost.Asio:用不同的`strand`s包裹几次
【发布时间】:2018-02-15 02:56:26
【问题描述】:

假设我有两个连接,每个连接都有各自的strand 以保证线程安全。这些连接不是单独运行的,它们可以以某种方式相互交谈。在此通信阶段,处理程序必须同步,这样任何两个处理程序都不能同时修改连接对象。

那么,为了实现这一点,我可以嵌套使用两个strand::wraps 吗?

例如,考虑以下伪代码:

class connection /* connection actually uses shared_ptr's to ensure lifetime */
{

public:

    connection *other       /* somehow set */;
    strand          strnd   /* somehow initialized correctly */;
    socket          sock    /* somehow initialized correctly */;
    streambuf       buf;

    int a   /* shared variable */;

    void trigger_read() // somewhat triggered
    {
        // since operations on sock are not thread-safe, use a strand to
        // synchronise them
        strnd.post([this] {
            // now consider the following code,
            async_read_until(sock, buf, '\n',
            this->strnd.wrap /* wrapping by this->strnd is for sure */([](...) {
                 // depending on the data, this handler can alter both other
                 // and this
                 other->a ++;   // not safe
                 this->a --;    // this is safe as protected by this->strnd
            }));

            // can protect them both by something like,
            async_read_until(sock, buf, '\n',
                this->strnd.wrap(other->strnd.wrap([](...) {
                    // depending on the data, this handler can alter both other
                    // and this
                    other->a ++;   // not safe
                    this->a --;    // this is safe as protected by this->strnd
            })));
            // this???
        });

    }

};

【问题讨论】:

  • 这是一个很难还是不好的问题:/
  • 哇。你应该买更多的耐心,马上。
  • 我喜欢这个问题,但回答起来并不容易(否则 OP 应该自己回答)。反对票不是我的,我用反对票反对。
  • @sehe 谢谢。如果问题很好,那就太好了!我觉得它可以帮助我进一步了解图书馆。兴奋地等待您的答复。

标签: c++ boost boost-asio asio


【解决方案1】:

您提出的建议在 Boost.Asio (1.65.1) 中不会没有潜在的竞争条件。考虑一下您下面帖子中的 sn-p,

// can protect them both by something like,
async_read_until(sock, buf, '\n',
    this->strnd.wrap(other->strnd.wrap([](...) {
        // depending on the data, this handler can alter both other
        // and this
        other->a ++;   // not safe
        this->a --;    // this is safe as protected by this->strnd
})));

并记得 strand::wrap 在调用时的行为与 strand::dispatch 相同。如果我们在这种情况下考虑它,那么我们可以推断出以下内容。但首先让我们使用 dispatch 和 lambdas 要求被包裹的 strands(用于说明目的)。

async_read_until(sock, buf, '\n', [...](...){  // lambda1
    this->strnd.dispatch([...]{                // lambda2
        other->strnd.dispatch([...]{           // lambda3
           other->a ++;
           this->a --:
        });
    });
 });

async_read_until 完成时,它将调用lambda1 的等价物,这将在连接自己的链上调用调度。无论是立即调用还是稍后调用,这都将导致在对this->a 的操作安全的设置中调用lambda2。在lambda2 中,我们调用other->stnd.dispatch,并保证lambda3 将被立即调用或发布到other->strnd。在任何一种情况下,lambda2 都会像this->strnd 提供的并发保证一样完成。如果lambda3 在最终被调用以访问this->a -- 时被发布到other->strnd,我们将不再有为调用lambda2 提供的保证。


检查标题

我们还可以通过以下反例检查strand_service (Boost 1.65) 中的do_dispatch 函数来看到这一点,因为wrap 在调用该函数时只是在链上调用dispatch

考虑一个由 2 股包裹的处理程序,strand1strand2

func = strand1.wrap(strand2.wrap(some_handler));

并且没有运行底层的io_service。然后当func 被调用时,由于我们不在当前链的调用中,dispatch 不会立即调用该函数,而是请求do_dispatch 执行进一步的处理。在do_dispatch 中,因为我们当前没有在 I/O 服务中运行,但没有其他处理程序拥有链的锁,do_dispatch 不会发出立即调用的信号,而是将处理程序推送到strand1 的就绪队列中。一旦进入就绪队列,一旦处理就绪队列,就会简单地调用处理程序(请参阅do_complete)。

这意味着strand2 的包装器被调用以在strand2strand1 上进行调度的点已经完全完成了提供它的保证。如果strand2 的调度调用导致立即调用,那么我们很好,如果发生冲突并且必须将处理程序推送到 strand2 的等待队列中,那么最终将不知道何时调用它。

总而言之,将处理程序包装在多个链中并不能保证该处理程序将处于具有所有链的并发保证的环境中。

【讨论】:

    猜你喜欢
    • 2016-10-02
    • 1970-01-01
    • 2013-07-06
    • 1970-01-01
    • 2014-02-12
    • 2012-08-21
    • 2014-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多