问题描述
我已经设计了一个具有以下类的原型应用程序:
I have designed a prototype app with the following classes:
- c> _ticker 的类别,它是$ a。
- BeatClock b $ b应该更新 _songpos ,这里我有一个问题...
- Ticker class (discussed here) that has an assignable callback that will be executed every tick (specified by _tickInterval)on a separate thread.
- SongPosition class that basically represents a position in a song.
- BeatClock class that assigns a callback to _ticker which issupposed to update _songpos and here I have a problem...
这是 Ticker 类实现的样子:
#include <cstdint> #include <functional> #include <chrono> #include <thread> #include <future> #include <mutex> class Ticker { public: /* TYPES: */ // Tick interval type in nanoseconds typedef std::chrono::nanoseconds tick_interval_type; // OnTick callback type typedef std::function<void()> on_tick_type; /* CONSTANTS: */ // Default ticker interval equal to 120 BPM static const tick_interval_type kDefaultTickerInterval; /* INIT: */ // Constructs object explicit Ticker (tick_interval_type tickInterval) : _onTick () , _tickInterval (tickInterval) , _running (false) {} // Destructs object ~Ticker () {} /* PROPERTIES: */ // Sets tick interval to a new value void setTickInterval (tick_interval_type tickInterval) { _tickIntervalMutex.lock(); _tickInterval = tickInterval; _tickIntervalMutex.unlock(); } // Sets on-tick callback to a new value void setOnTick (on_tick_type onTick) { _onTick = onTick; } /* PUBLIC METHODS: */ // Starts the ticker void start () { if (_running) return; _running = true; std::thread run( &Ticker::loop, this ); run.detach(); } // Stops the ticker void stop () { _running = false; } private: /* FIELDS: */ // OnTick callback, called every tick on_tick_type _onTick; // Tick interval between every tick tick_interval_type _tickInterval; // Ticker running flag volatile bool _running; // Tick interval mutex for thread safety std::mutex _tickIntervalMutex; /* PRIVATE METHODS: */ // Inner loop executed on a separate thread to produce ticks void loop () { tick_interval_type ti; while (_running) { std::thread run (_onTick); run.detach(); _tickIntervalMutex.lock(); ti = _tickInterval; _tickIntervalMutex.unlock(); std::this_thread::sleep_for( ti ); } } }; // Default ticker interval equal to 120 BPM const Ticker::tick_interval_type Ticker::kDefaultTickerInterval (625000000UL / 120UL);
这是 SongPosition 类实现看起来像:
#include <cstdint> class SongPosition { public: /* INIT: */ // Constructs song position instance: Ticks | Beats | Measures SongPosition (uint8_t ticks = 0u, uint8_t beats = 0u, uint16_t measures = 0u) { _ticks = ticks; _beats = beats; _measures = measures; } ~SongPosition () {} /* PROPERTIES:: */ // Counts from 0..95 uint8_t ticks () const { return _ticks; } // Counts the quarter notes 0..3 uint8_t beats () const { return _beats; } // Counts the measures 0..65535 uint16_t measures () const { return _measures; } // Current meter location, sixteens-notes counter uint16_t meter () const { return (_beats << 2) | (_measures << 4); } /* METHODS: */ // Resets meter counters void reset () { _ticks = 0u; _beats = 0u; _measures = 0u; } // Resets ticks counter void resetTicks () { _ticks = 0u; } // Resets beats counter void resetBeats () { _beats = 0u; } // Increments ticks counter void incrementTicks () { ++_ticks; } // Increments beats counter void incrementBeats () { ++_beats; } // Increments measures counter void incrementMeasures () { ++_measures; } // Decrements measures counter void decrementMeasures () { --_measures; } bool operator== (const SongPosition& other) const { if (_ticks == other.ticks() && _beats == other.beats() && _measures == other.measures()) return true; else return false; } bool operator!= (const SongPosition& other) const { return !(*this == other); } private: /* FIELDS: */ // Counts from 0..95 uint8_t _ticks; // Counts the quarter notes 0..3 uint8_t _beats; // Counts the measures 0..65535 uint16_t _measures; }; inline bool operator< (const SongPosition& lhs, const SongPosition& rhs) { if (lhs.measures() < rhs.measures()) return true; else if (lhs.measures() == rhs.measures()) { if (lhs.beats() < rhs.beats()) return true; else if (lhs.beats() == rhs.beats()) { if (lhs.ticks() < rhs.ticks()) return true; else return false; } else return false; } else return false; } inline bool operator> (const SongPosition& lhs, const SongPosition& rhs) { return rhs < lhs; } inline bool operator<= (const SongPosition& lhs, const SongPosition& rhs) { return !(lhs > rhs); } inline bool operator>= (const SongPosition& lhs, const SongPosition& rhs) { return !(lhs < rhs); }
最后这里是 BeatClock 类实现看起来像(问题一):
And finally here is what BeatClock class implementation looks like (the problem one):
#include <cstdint> #include "SongPosition.h" #include "Ticker.h" class BeatClock { public: /* INNER CLASSES: */ // Inner state of beat clock enum class State : uint8_t { kStopped, kRunning, kPaused }; /* CONSTANTS: */ // Clock pre-start delay to give a slave time to prepare (1ms) static const std::chrono::milliseconds kPreStartDelay; /* CONSTRUCTORS/DISTRUCTORS: */ // Constructs object BeatClock () : _bpm (120UL) , _ticker (Ticker::kDefaultTickerInterval) , _songpos () , _precount () { _ticker.setOnTick( [&] { _tickerMutex.lock(); _songpos.incrementTicks(); if (_songpos.ticks() == 96U) { // next beat... _songpos.resetTicks(); _songpos.incrementBeats(); if (_songpos.beats() == 4U) { // next measure... _songpos.resetBeats(); _songpos.incrementMeasures(); } } if (_precount == 0U) { _precount = 4U; printf( "Send - RealTimeClock: %2i | %1i | %05i", _songpos.ticks(), _songpos.beats(), _songpos.ticks() ); } ++_precount; _tickerMutex.unlock(); } ); _state = State::kStopped; } // Destructs object ~BeatClock () {} /* PROPERTIES: */ // Gets current BPM value uint64_t bpm () const { return _bpm; } // Sets current BPM to a new value void setBpm (uint64_t bpm) { _bpm = bpm; Ticker::tick_interval_type span (625000000UL / _bpm); _ticker.setTickInterval( span ); } /* PUBLIC METHODS: */ // Starts if currently stopped / Pauses if currently running / Resumes if currently paused void start () { switch (_state) { case State::kStopped: // start... _songpos.reset(); this->sendMeter(); printf( "Send - RealTimeStart" ); std::this_thread::sleep_for( kPreStartDelay ); _state = State::kRunning; _ticker.start(); break; case State::kRunning: // pause... _ticker.stop(); _state = State::kPaused; break; case State::kPaused: // continue... printf( "Send - RealTimeContinue" ); _state = State::kRunning; _ticker.start(); break; } } // Stops if currently running or paused / Resets if currently stopped void stop () { switch (_state) { case State::kStopped: // reset... _songpos.reset(); this->sendMeter(); printf( "Send - RealTimeStop" ); break; case State::kRunning: case State::kPaused: // stop... printf( "Send - RealTimeStop" ); _ticker.stop(); _precount = 0U; _state = State::kStopped; break; } } // Increments measure and resets sub-counters void forward () { if (_state == State::kRunning) _ticker.stop(); _songpos.resetTicks(); _songpos.resetBeats(); _songpos.incrementMeasures(); _precount = 0U; this->sendMeter(); if (_state == State::kRunning) _ticker.start(); } // Decrements measure and resets sub-counters void rewind () { if (_state == State::kRunning) _ticker.stop(); _songpos.resetTicks(); _songpos.resetBeats(); if (_songpos.measures() > 0U) _songpos.decrementMeasures(); _precount = 0U; this->sendMeter(); if (_state == State::kRunning) _ticker.start(); } private: /* FIELDS: */ // Current BPM value uint64_t _bpm; // Used for generating tick events. Ticker _ticker; // Current song position SongPosition _songpos; // Clock precount to scale from 96 to 24 ppqn (0..3) uint8_t _precount; // Transport state State _state; // Ticker mutex for thread safety std::mutex _tickerMutex; /* PRIVATE METHODS: */ // Sends Song-Position MIDI message void sendMeter () { printf( "Send - SongPosition: %i", _songpos.meter() ); } }; // Clock pre-start delay to give a slave time to prepare (1ms) const std::chrono::milliseconds BeatClock::kPreStartDelay (1);
_onTick 回调不能正常工作,更新 _songpos 随机(因为数据竞争?)
你能否请教如何更新 _songpos
_onTick callback is not working as expected, updating _songpos randomly (because of data-races?)Can you please advice how to update _songpos in controllable way from a callback (which is running on a separate thread)?
这是我期望记录的(即每拍24拍和4拍每个度量):
Here is what I'm expecting to be logged (that is 24 ticks per beat and 4 beats per measure):
Send - SongPosition: 0 Send - RealTimeStart // begin... Send - RealTimeClock: 2 | 0 | 00000 Send - RealTimeClock: 5 | 0 | 00000 Send - RealTimeClock: 9 | 0 | 00000 Send - RealTimeClock: 13 | 0 | 00000 Send - RealTimeClock: 17 | 0 | 00000 Send - RealTimeClock: 21 | 0 | 00000 Send - RealTimeClock: 25 | 0 | 00000 Send - RealTimeClock: 29 | 0 | 00000 Send - RealTimeClock: 33 | 0 | 00000 Send - RealTimeClock: 37 | 0 | 00000 Send - RealTimeClock: 41 | 0 | 00000 Send - RealTimeClock: 45 | 0 | 00000 Send - RealTimeClock: 49 | 0 | 00000 Send - RealTimeClock: 53 | 0 | 00000 Send - RealTimeClock: 57 | 0 | 00000 Send - RealTimeClock: 61 | 0 | 00000 Send - RealTimeClock: 65 | 0 | 00000 Send - RealTimeClock: 69 | 0 | 00000 Send - RealTimeClock: 73 | 0 | 00000 Send - RealTimeClock: 77 | 0 | 00000 Send - RealTimeClock: 81 | 0 | 00000 Send - RealTimeClock: 85 | 0 | 00000 Send - RealTimeClock: 89 | 0 | 00000 Send - RealTimeClock: 93 | 0 | 00000 Send - RealTimeClock: 1 | 1 | 00000 // next beat.. Send - RealTimeClock: 5 | 1 | 00000 Send - RealTimeClock: 9 | 1 | 00000 Send - RealTimeClock: 13 | 1 | 00000 Send - RealTimeClock: 17 | 1 | 00000 Send - RealTimeClock: 21 | 1 | 00000 Send - RealTimeClock: 25 | 1 | 00000 Send - RealTimeClock: 29 | 1 | 00000 Send - RealTimeClock: 33 | 1 | 00000 Send - RealTimeClock: 37 | 1 | 00000 Send - RealTimeClock: 41 | 1 | 00000 Send - RealTimeClock: 45 | 1 | 00000 Send - RealTimeClock: 49 | 1 | 00000 Send - RealTimeClock: 53 | 1 | 00000 Send - RealTimeClock: 57 | 1 | 00000 Send - RealTimeClock: 61 | 1 | 00000 Send - RealTimeClock: 65 | 1 | 00000 Send - RealTimeClock: 69 | 1 | 00000 Send - RealTimeClock: 73 | 1 | 00000 Send - RealTimeClock: 77 | 1 | 00000 Send - RealTimeClock: 81 | 1 | 00000 Send - RealTimeClock: 85 | 1 | 00000 Send - RealTimeClock: 89 | 1 | 00000 Send - RealTimeClock: 93 | 1 | 00000 Send - RealTimeClock: 1 | 2 | 00000 // next beat.. Send - RealTimeClock: 5 | 2 | 00000 Send - RealTimeClock: 9 | 2 | 00000 Send - RealTimeClock: 13 | 2 | 00000 Send - RealTimeClock: 17 | 2 | 00000 Send - RealTimeClock: 21 | 2 | 00000 Send - RealTimeClock: 25 | 2 | 00000 Send - RealTimeClock: 29 | 2 | 00000 Send - RealTimeClock: 33 | 2 | 00000 Send - RealTimeClock: 37 | 2 | 00000 Send - RealTimeClock: 41 | 2 | 00000 Send - RealTimeClock: 45 | 2 | 00000 Send - RealTimeClock: 49 | 2 | 00000 Send - RealTimeClock: 53 | 2 | 00000 Send - RealTimeClock: 57 | 2 | 00000 Send - RealTimeClock: 61 | 2 | 00000 Send - RealTimeClock: 65 | 2 | 00000 Send - RealTimeClock: 69 | 2 | 00000 Send - RealTimeClock: 73 | 2 | 00000 Send - RealTimeClock: 77 | 2 | 00000 Send - RealTimeClock: 81 | 2 | 00000 Send - RealTimeClock: 85 | 2 | 00000 Send - RealTimeClock: 89 | 2 | 00000 Send - RealTimeClock: 93 | 2 | 00000 Send - RealTimeClock: 1 | 3 | 00000 // next beat.. Send - RealTimeClock: 5 | 3 | 00000 Send - RealTimeClock: 9 | 3 | 00000 Send - RealTimeClock: 13 | 3 | 00000 Send - RealTimeClock: 17 | 3 | 00000 Send - RealTimeClock: 21 | 3 | 00000 Send - RealTimeClock: 25 | 3 | 00000 Send - RealTimeClock: 29 | 3 | 00000 Send - RealTimeClock: 33 | 3 | 00000 Send - RealTimeClock: 37 | 3 | 00000 Send - RealTimeClock: 41 | 3 | 00000 Send - RealTimeClock: 45 | 3 | 00000 Send - RealTimeClock: 49 | 3 | 00000 Send - RealTimeClock: 53 | 3 | 00000 Send - RealTimeClock: 57 | 3 | 00000 Send - RealTimeClock: 61 | 3 | 00000 Send - RealTimeClock: 65 | 3 | 00000 Send - RealTimeClock: 69 | 3 | 00000 Send - RealTimeClock: 73 | 3 | 00000 Send - RealTimeClock: 77 | 3 | 00000 Send - RealTimeClock: 81 | 3 | 00000 Send - RealTimeClock: 85 | 3 | 00000 Send - RealTimeClock: 89 | 3 | 00000 Send - RealTimeClock: 93 | 3 | 00000 Send - RealTimeClock: 1 | 0 | 00001 // next measure and once again... Send - RealTimeStop // enough. :)
非常感谢!
推荐答案
我已经测试过你的代码,它工作正常,我没有注意到任何竞争条件。考虑这个字符串 log(Send - RealTimeClock:%2i |%1i |%05i,_songpos.ticks(),_songpos.beats(),_songpos.ticks());
它写入 ticks |节拍| ticks 每隔一秒钟。也许,你的意思是措施,而不是一个蜱?正确的输出如下所示(最后一个值是度量)
I have tested your code and it works fine and I haven't noticed any race condition. Consider this string log( "Send - RealTimeClock: %2i | %1i | %05i", _songpos.ticks(), _songpos.beats(), _songpos.ticks() );It writes ticks | beats | ticks about every one second. Perhaps, you mean measures instead one of ticks? Proper output looks like the following (the last value is a measure)
Send - SongPosition: 0, Send - RealTimeStart Send - RealTimeClock: 1 | 0 | 0 Send - RealTimeClock: 61 | 2 | 0 Send - RealTimeClock: 25 | 1 | 1 Send - RealTimeClock: 85 | 3 | 1 Send - RealTimeClock: 49 | 2 | 2 Send - RealTimeClock: 13 | 1 | 3
这是确定。你可以检查它是否是竞态条件,只是将线程启动更改为简单函数调用。
And it is ok. You can check if it is race condition just changing thread start to simple function invocation.
还有一句话。 _tickerMutex 在您的情况下似乎是不安全的。如果您使用mutex来保护某些数据不会同时发生更改,请在更改数据时在 每 种情况下使用。因此_ songpos 在您的代码lambda函数中受到保护,但在所有函数(如开始,停止等)中都是不安全的。
And one remark. _tickerMutex appears to be unsafe in your case. If you use mutex to protect some data from simultaneous change then use it in every case when the data is changed. So _songpos is protected in your ticker lambda function but is unsafe in all functions like start, stop etc.
这篇关于C ++ 11原型应用程序设计多线程问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!