Boost logo

Boost :

From: William E. Kempf (wekempf_at_[hidden])
Date: 2003-02-12 12:12:19


Sorry for late reply... had a hard disk problem that prevented accessing
e-mail.

Peter Dimov said:
> William E. Kempf wrote:
>>
>> How about this compromise:
>>
>> template <typename R>
>> class async_call
>> {
>> public:
>> template <typename F>
>> explicit async_call(const F& f)
>> : m_func(f)
>> {
>> }
>>
>> void operator()()
>> {
>> mutex::scoped_lock lock(m_mutex);
>> if (m_result)
>> throw "can't call multiple times";
>
> operator() shouldn't throw; it's being used as a thread procedure, and
> the final verdict on these was to terminate() on exception, I believe.
> But you may have changed that. :-)

I'm not sure how the terminate() on exception semantics (which haven't
changed) apply, exactly. But I assume you (and probably Dave) would
prefer this to just be an assert and documented undefined behavior. I
have no problems with that.

>> lock.unlock();
>> R temp(m_func());
>> lock.lock();
>> m_result.reset(temp);
>> m_cond.notify_all();
>> }
>>
>> R result() const
>> {
>> boost::mutex::scoped_lock lock(m_mutex);
>> while (!m_result)
>> m_cond.wait(lock);
>
> This changes result()'s semantics to "block until op() finishes"; what
> happens if nobody calls op()? Or it throws an exception?

Changes the semantics? I thought this was what was expected and
illustrated in every example thus far? Failure to call op() is a user
error that will result in deadlock if result() is called. The only other
alternative is to throw in result() if op() wasn't called, but I don't
think that's appropriate. The exception question still needs work. We
probably want result() to throw in this case, the question is what it will
throw. IOW, do we build the mechanism for propogating exception types
across thread boundaries, or just throw a single generic exception type.

>> return *m_result.get();
>> }
>>
>> private:
>> boost::function0<R> m_func;
>> optional<R> m_result;
>> mutable mutex m_mutex;
>> mutable condition m_cond;
>> };
>>
>> template <typename R>
>> class future
>> {
>> public:
>> template <typename F>
>> explicit future(const F& f)
>> : m_pimpl(new async_call<R>(f))
>> {
>> }
>>
>> future(const future<R>& other)
>> {
>> mutex::scoped_lock lock(m_mutex);
>
> I don't think you need a lock here, but I may be missing something.

I have to double check the implementation of shared_ptr<>, but I was
assuming all it did was to synchronize the ref count manipulation.
Reads/writes of the data pointed at needed to be synchronized externally.
If that's the case, the assignment here needs to be synchronized in order
to insure it doesn't interrupt the access in op().

>> m_pimpl = other.m_pimpl;
>> }
>>
>> future<R>& operator=(const future<R>& other)
>> {
>> mutex::scoped_lock lock(m_mutex);
>
> -"-
>
>> m_pimpl = other.m_pimpl;
>> }
>>
>> void operator()()
>> {
>> (*get())();
>> }
>>
>> R result() const
>> {
>> return get()->result();
>> }
>>
>> private:
>> shared_ptr<async_call<R> > get() const
>> {
>> mutex::scoped_lock lock(m_mutex);
>
> -"-
>
>> return m_pimpl;
>> }
>>
>> shared_ptr<async_call<R> > m_pimpl;
>> mutable mutex m_mutex;
>> };
>
> As for the "big picture", ask Dave. ;-) I tend towards a refcounted
> async_call.

That's what future<> gives you, while async_call<> requires no dynamic
memory allocation, which is an important consideration for many uses.

-- 
William E. Kempf

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk