// bslmt_turnstile.h -*-C++-*- #ifndef INCLUDED_BSLMT_TURNSTILE #define INCLUDED_BSLMT_TURNSTILE #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a mechanism to meter time. // //@CLASSES: // bslmt::Turnstile: mechanism to meter time // //@DESCRIPTION: This component provides a mechanism, 'bslmt::Turnstile', to // meter time. A turnstile is configured with a rate that specified how many // "events" per second the turnstile should allow. After the rate is set (via // the constructor or the 'reset' method), callers may execute the 'waitTurn' // method, which blocks until the next interval arrives. If the turnstile is // not called at or above the configured rate (e.g., due to processing // performed at each interval), the turnstile is said to be "lagging behind." // The amount of lag time is obtained from the 'lagTime' method. // ///Comparison with Sleep ///--------------------- // A straightforward implementation of metering is to call some form of sleep // (e.g., 'bslmt::ThreadUtil::microSleep') with a computed rate after each // processing step. However, simply calling "sleep" accumulates errors since // this implementation does not account for the time taken during the // processing step. For example, given two functions that take 'rate' (turns // per second) and 'duration' (expected execution time in seconds), and execute // 'rate * duration' calls to 'bsl::sqrt', calling 'waitTurn' on a turnstile or // 'bslmt::ThreadUtil::microSleep' with duration '1000000 / rate', // respectively; the elapsed time for each call results in the following table, // showing that the 'bslmt::Turnstile' implementation maintains the correct // rate while the 'microSleep' implementation accumulates errors. //.. // Elapsed Time // Rate Duration Turnstile Sleep // ---- -------- --------- ----- // 10 1 0.900310 0.940390 // 100 1 0.980853 1.609041 // 500 1 1.000711 4.989093 // 1000 1 1.000103 9.988734 //.. // ///Thread Safety ///------------- // Except for the 'reset' method, this component is thread-safe and // thread-aware, meaning that multiple threads may safely use their own // instances or a shared instance of a 'bslmt::Turnstile' object, provided that // 'reset' is not called on a turnstile object while another thread is // accessing or modifying the same object. // ///Timer Resolution ///---------------- // The 'waitTurn' method has a resolution of 10 milliseconds. Therefore, // 'bslmt::Turnstile' cannot guarantee that all turns can be taken in each one // second interval if a rate higher than 100 turns per second is specified. // ///Usage ///----- // The following example illustrates the use of 'bslmt::Turnstile' to control // the rate of output being written to a specified output stream. The example // function, 'heartbeat', prints a specified message at a specified rate for a // specified duration. An instance of 'bsls::Stopwatch' is used to measure // time against the specified duration. //.. // static void heartbeat(bsl::ostream& stream, // const bsl::string& message, // double rate, // double duration) // { // // Write the specified 'message' to the specified 'stream' at the // // specified 'rate' (given in messages per second) for the specified // // 'duration'. // // bsls::Stopwatch timer; // timer.start(); // bslmt::Turnstile turnstile(rate); // // while (true) { // turnstile.waitTurn(); // if (timer.elapsedTime() >= duration) { // break; // } // stream << message; // } // } //.. // The benefits of using 'bslmt::Turnstile' in the above example, as opposed to // simply calling 'sleep' in a loop, are twofold. Firstly, 'bslmt::Turnstile' // automatically accounts for drift caused by additional processing, so the // loop is allowed to execute immediately if the program fails to execute the // loop at the specified 'rate'. Secondly, computing the sleep time and // executing the sleep call, are encapsulated in the turnstile component, which // improves the overall readability of the program. #include <bslscm_version.h> #include <bsls_atomic.h> #include <bsls_timeinterval.h> #include <bsls_types.h> namespace BloombergLP { namespace bslmt { // =============== // class Turnstile // =============== class Turnstile { // This class provides a mechanism to meter time. Using either the // constructor or the 'reset' method, the client specifies 'rate', // indicating the number of events per second that the turnstile will // allow. The client then calls 'waitTurn', which will either sleep until // the next event is to occur, or return immediately if 'waitTurn' was // called after the next event is due. If 'waitTurn' is not called until // after the next event is due, the turnstile is said to be 'lagging' // behind, and calls to 'waitTurn' will not sleep until the events have // caught up with the schedule. Note that calling 'waitTurn' a single time // does not bring a turnstile back on schedule. For example, if a // turnstile's configured frequency is one event per second, and the client // is 10 seconds behind schedule, if 'waitTurn' were subsequently called // once per second, the turnstile will remain at 10 seconds behind // schedule. The amount by which events are lagging behind the schedule // can be determined via the 'lagTime' method, which returns the positive // number of microseconds by which the turnstile is lagging, or 0 if the // turnstile is not behind schedule. // DATA bsls::AtomicInt64 d_nextTurn; // absolute time of next turn in // microseconds bsls::Types::Int64 d_interval; // interval time in microseconds mutable bsls::AtomicInt64 d_timestamp; // time of last call to 'now' in // microseconds int d_minTimeToCallSleep; // shortest period of time, in // microseconds, that the // 'waitTurn' function will go to // sleep // PRIVATE TYPES typedef bsls::Types::Int64 Int64; private: // NOT IMPLEMENTED Turnstile(const Turnstile&); Turnstile& operator=(const Turnstile&); public: // CREATORS explicit Turnstile(double rate, const bsls::TimeInterval& startTime = bsls::TimeInterval(0), const bsls::TimeInterval& minTimeToCallSleep = bsls::TimeInterval(1e-4)); // Create a turnstile object that admits clients at the specified // 'rate', expressed as the number of turns per second. Optionally // specify the (relative) 'startTime' of the first turn. If // 'startTime' is not specified, the first turn may be taken // immediately. Optionally specify 'minTimeToCallSleep', the shortest // period of time in seconds that the 'waitTurn' function will go to // sleep. The behavior is undefined unless '0 < rate'. // ~Turnstile(); // Destroy this object. Note that this trivial destructor is generated // by the compiler. // MANIPULATORS void reset(double rate, const bsls::TimeInterval& startTime = bsls::TimeInterval(0), const bsls::TimeInterval& minTimeToCallSleep = bsls::TimeInterval(1e-4)); // Reset the rate of this turnstile to the specified 'rate', expressed // as the number of turns per second. Optionally specify the // (relative) 'startTime' of the first turn. If 'startTime' is not // specified, the first turn may be taken immediately. Optionally // specify 'minTimeToCallSleep', the shortest period of time in // seconds that the 'waitTurn' function will go to sleep. The behavior // is undefined unless '0 < rate'. Note that threads blocked on // 'waitTurn' are not interrupted. bsls::Types::Int64 waitTurn(bool sleep = true); // Sleep until the next turn may be taken or return immediately if the // optionally specified 'sleep' is false, this turnstile is lagging // behind schedule, or if the calculated sleep duration is less than // 'minTimeToCallSleep'. Return the non-negative number of // microseconds spent waiting, or if 'sleep' is false, the number of // microseconds the function would have had to wait. // ACCESSORS bsls::Types::Int64 lagTime() const; // Return the positive number of microseconds difference between the // the current time and the next turn, indicating that the turnstile is // lagging behind the configured rate. Otherwise, return 0. }; } // close package namespace } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2015 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------