Modelling concepts referring to <chrono>

54 views
Skip to first unread message

Marc Mutz

unread,
Oct 17, 2016, 8:40:23 AM10/17/16
to std-dis...@isocpp.org
Hi,

In Qt, we've stumbled across the following issue, when trying to make QMutex
model TimedLockable:

We model it by providing inline methods try_lock{,_for,_until}() that delegate
to the legacy QMutex functions that take an int representing milliseconds.

So far so simple.

The question is what type try_lock_for() should take. Remember, QMutex
fundamentally only supports millisecond granularity, so we could either take
any duration and perform the duration_cast to milliseconds inside the
function, breaking a design rule of <chrono>, namely that narrowing duration
conversions should be explicit, or just take std::chrono::milliseconds,
forcing users to be explicit about the narrowing, using a duration_cast at the
call site.

By hiding the conversion inside the try_lock_for function, we make it
implicit, so misleading code such as

mutex.try_lock_for(10us);

compiles, but just does a try_lock(), without any waiting time.

If we take only milliseconss, does this still model TimedLockable?

More generally, if the underlying API (Qt, platform-native, machine-native,
...) cannot deal with a given (finer) resolution, should the API support it?
Should, say,

using femtoseconds = std::chrono::duration<int, std::femto>;
std::decltype<std::timed_mutex&>().try_lock_for((femtoseconds(10));

compile?

Thanks,
Marc

--
Marc Mutz <marc...@kdab.com> | Senior Software Engineer
KDAB (Deutschland) GmbH & Co.KG, a KDAB Group Company
Tel: +49-30-521325470
KDAB - The Qt, C++ and OpenGL Experts

Howard Hinnant

unread,
Oct 17, 2016, 10:52:29 AM10/17/16
to std-dis...@isocpp.org
I recommend implementing try_lock_for in terms of try_lock_until and a steady clock. For example:

template <class Rep, class Period>
bool
try_lock_for(const std::chrono::duration<Rep, Period>& d)
{return try_lock_until(std::chrono::steady_clock::now() + d);}

Now the question becomes: what if the time_point has finer precision than you can deal with?

For a timed mutex wait, you should not return _prior_ to the desired time out unless the mutex is locked. The specification allows arbitrarily long delays in returning without the lock after the time out has expired (though quality implementations will try to minimize such a delay).

So if the time out has precision of femto seconds: Tf, but you can only measure now() to a precision of milliseconds: Tm, you should not return without the lock until Tm >= Tf.

Note that in chrono, _all_ comparisons are exact, even when the precision of the arguments is different. If Tf happens to specify a time point that is in between Tm and Tm - 1ms, then the above rule implies that you try for the lock until the next measurable now() after Tf.

In summary, it is not that there is an implicit conversion from Tf to Tm, it is that delays in timing out are inevitable, due to a wide variety of reasons including thread preemption and the cost of checking the current time. Rounding up to the next measurable millisecond is just another example of such “quality of management” delays which 30.2.4 [thread.req.timing] refers to as Dm.

Howard

Marc Mutz

unread,
Oct 18, 2016, 12:32:32 PM10/18/16
to std-dis...@isocpp.org, Howard Hinnant
On Monday 17 October 2016 16:52:26 Howard Hinnant wrote:
> I recommend implementing try_lock_for in terms of try_lock_until and a
> steady clock. For example:
>
> template <class Rep, class Period>
> bool
> try_lock_for(const std::chrono::duration<Rep, Period>& d)
> {return try_lock_until(std::chrono::steady_clock::now() + d);}
>
> Now the question becomes: what if the time_point has finer precision than
> you can deal with?
>
> For a timed mutex wait, you should not return _prior_ to the desired time
> out unless the mutex is locked. The specification allows arbitrarily long
> delays in returning without the lock after the time out has expired
> (though quality implementations will try to minimize such a delay).
>
> So if the time out has precision of femto seconds: Tf, but you can only
> measure now() to a precision of milliseconds: Tm, you should not return
> without the lock until Tm >= Tf.
>
> Note that in chrono, _all_ comparisons are exact, even when the precision
> of the arguments is different. If Tf happens to specify a time point that
> is in between Tm and Tm - 1ms, then the above rule implies that you try
> for the lock until the next measurable now() after Tf.

Ok, so the API accepts all precisions, but uses the equivalent of C++17
std::ceil for time_points instead of time_point_cast to snap to the precision
of the underlying API. That makes sense.

Howard Hinnant

unread,
Oct 18, 2016, 12:36:41 PM10/18/16
to ISO C++ Standard - Discussion

> On Oct 18, 2016, at 12:34 PM, Marc Mutz <marc...@kdab.com> wrote:
>
> On Monday 17 October 2016 16:52:26 Howard Hinnant wrote:
>> I recommend implementing try_lock_for in terms of try_lock_until and a
>> steady clock. For example:
>>
>> template <class Rep, class Period>
>> bool
>> try_lock_for(const std::chrono::duration<Rep, Period>& d)
>> {return try_lock_until(std::chrono::steady_clock::now() + d);}
>>
>> Now the question becomes: what if the time_point has finer precision than
>> you can deal with?
>>
>> For a timed mutex wait, you should not return _prior_ to the desired time
>> out unless the mutex is locked. The specification allows arbitrarily long
>> delays in returning without the lock after the time out has expired
>> (though quality implementations will try to minimize such a delay).
>>
>> So if the time out has precision of femto seconds: Tf, but you can only
>> measure now() to a precision of milliseconds: Tm, you should not return
>> without the lock until Tm >= Tf.
>>
>> Note that in chrono, _all_ comparisons are exact, even when the precision
>> of the arguments is different. If Tf happens to specify a time point that
>> is in between Tm and Tm - 1ms, then the above rule implies that you try
>> for the lock until the next measurable now() after Tf.
>
> Ok, so the API accepts all precisions, but uses the equivalent of C++17
> std::ceil for time_points instead of time_point_cast to snap to the precision
> of the underlying API. That makes sense.

Indeed, implementing these was when I first became aware of the utility of ceil for time_points. :-)

Howard

Reply all
Reply to author
Forward
0 new messages