diff --git a/CMakeLists.txt b/CMakeLists.txt index 783630d0..0be96064 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,6 +270,8 @@ list ( job/flag.hpp job/queue.cpp job/queue.hpp + job/ticketlock.cpp + job/ticketlock.hpp job/spinlock.cpp job/spinlock.hpp json/fwd.hpp @@ -483,6 +485,7 @@ if (TESTS) job/flag job/queue job/spinlock + job/ticketlock json_types json2/event maths diff --git a/job/ticketlock.cpp b/job/ticketlock.cpp new file mode 100644 index 00000000..8d2a9a5e --- /dev/null +++ b/job/ticketlock.cpp @@ -0,0 +1,45 @@ +/* + * 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. + * + * Copyright 2018 Danny Robson + */ + +#include "ticketlock.hpp" + +using util::job::ticketlock; + + +/////////////////////////////////////////////////////////////////////////////// +ticketlock::ticketlock (): + current (0), + next (0) +{ } + + +/////////////////////////////////////////////////////////////////////////////// +void +ticketlock::lock (void) +{ + auto self = next++; + + while (current != self) + __asm__ volatile ("pause"); +} + + +//----------------------------------------------------------------------------- +void +ticketlock::unlock (void) +{ + ++current; +} diff --git a/job/ticketlock.hpp b/job/ticketlock.hpp new file mode 100644 index 00000000..6a76197f --- /dev/null +++ b/job/ticketlock.hpp @@ -0,0 +1,36 @@ +/* + * 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. + * + * Copyright 2018 Danny Robson + */ + +#ifndef CRUFT_UTIL_JOB_TICKETLOCK_HPP +#define CRUFT_UTIL_JOB_TICKETLOCK_HPP + +#include + +namespace util::job { + class ticketlock { + public: + ticketlock (); + + void lock (void); + void unlock (void); + + private: + std::atomic current; + std::atomic next; + }; +}; + +#endif diff --git a/test/job/ticketlock.cpp b/test/job/ticketlock.cpp new file mode 100644 index 00000000..d53a7499 --- /dev/null +++ b/test/job/ticketlock.cpp @@ -0,0 +1,93 @@ +/* + * 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. + * + * Copyright 2018 Danny Robson + */ + +#include "job/flag.hpp" +#include "job/ticketlock.hpp" +#include "tap.hpp" + +#include + +#include +#include + + +/////////////////////////////////////////////////////////////////////////////// +using ffs_t = std::chrono::high_resolution_clock; + + +//----------------------------------------------------------------------------- +void +fight (util::job::flag &start, util::job::ticketlock &l, int total, ffs_t::time_point &finish) { + start.wait (); + + for (int count = 0; count < total; ++count) + std::lock_guard g (l); + + finish = std::chrono::high_resolution_clock::now (); +}; + + + +//----------------------------------------------------------------------------- +int +main () +{ + util::TAP::logger tap; + + util::job::ticketlock l; + l.lock (); + tap.expect (true, "locked without contention"); + + l.unlock (); + tap.expect (true, "unlocked without contention"); + + if (std::thread::hardware_concurrency () < 2) { + tap.skip ("reasonably fair under contention"); + } else { + // measure fairness under contention is below some threshold + // + // * create two threads that will fight over a lock + // * measure the start time + // * fire an event to start the threads + // * record the time the threads finish + // * compare the difference in runtimes as a percentage + // + // we have to be reasonably generous with our test at the end because + // there's liable to be a lot of noise in the measurement with a test + // that runs in a short enough time period. + constexpr int iterations = 1 << 16; + util::job::flag start_flag; + + ffs_t::time_point a_finish, b_finish; + + std::thread a (fight, std::ref (start_flag), std::ref (l), iterations, std::ref (a_finish)); + std::thread b (fight, std::ref (start_flag), std::ref (l), iterations, std::ref (b_finish)); + + auto start = std::chrono::high_resolution_clock::now (); + start_flag.notify (); + + a.join (); + b.join (); + + auto a_total = std::chrono::duration_cast (a_finish - start).count (); + auto b_total = std::chrono::duration_cast (b_finish - start).count (); + + auto diff = util::max (a_total, b_total) / float (util::min (a_total, b_total)); + auto rel = util::abs (1 - diff); + std::cout << rel << '\n'; + tap.expect_lt (rel, 0.1f, "reasonably fair under contention"); + } +}