Boost.Asio:用不同的`strand`s包裹几次

时间:2017-09-06 14:45:19

标签: c++ boost boost-asio asio

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

所以,为了达到这个目的,我可以以嵌套的方式使用两个strand::wrap吗?

例如,请考虑以下伪代码:

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

    }

};

1 个答案:

答案 0 :(得分: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和lambda来包装这些束(出于说明目的)。

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

async_read_until完成后,它将调用lambda1的等效项,后者将在连接自身的子线上调用dispatch。无论是立即调用还是稍后调用,这都将导致lambda2在安全的设置中被调用,以操纵this->a。在lambda2中,我们调用other->stnd.dispatch,并通过该链lambda3的保证将立即调用或发布到other->strnd 。无论哪种情况,lambda2都会完成,this->strnd所提供的并发保证也会完成。如果lambda3在最终被调用以访问other->strnd时被发布到this->a --上,我们将不再为调用lambda2提供保证。


检查标题

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

考虑一个由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的等待队列中,则无法确定何时将最终调用该处理程序。

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