0

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

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

例如,考虑以下伪代码:

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???
        });

    }

};
4

1 回答 1

0

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

// 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 要求被包裹的链(为了说明目的)。

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

完成async_read_until后,它将调用等价物,该等价物lambda1将在连接自己的链上调用分派。无论是立即调用还是稍后调用,这将导致在lambda2this->a. 在lambda2我们调用other->stnd.dispatchwhich 并通过该 strand 的保证lambda3将立即调用或发布到other->strnd. 在任何一种情况下,都与 .lambda2提供的并发保证一样完成this->strnd。如果lambda3发布到other->strnd它最终被调用访问时,this->a --我们将不再有为调用提供的保证lambda2


检查标题

我们还可以通过使用以下反例检查(Boost 1.65)do_dispatch中的函数来 看到这一点,就像在调用函数时简单地在链上调用一样。strand_servicewrapdispatch

考虑一个由 2 股包裹的处理程序,strand1并且strand2

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

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

这意味着strand2调用 's 包装器以调度 onstrand2strand1点完全通过提供它的保证来完成。如果strand2的调度调用导致立即调用,那么我们很好,如果存在冲突并且必须将处理程序推入 strand2 的等待队列,那么最终何时调用它就无法确定了。

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

于 2018-07-23T08:05:40.353 回答