C ++ 11原型应用程序设计多线程问题 [英] C++11 prototype app design multithreading issue
问题描述
我已经设计了一个具有以下类的原型应用程序:
I have designed a prototype app with the following classes:
-
Ticker
类(此处讨论),它具有可分配的回调,将在每个tick(由_tickInterval
)
在一个单独的线程。 -
SongPosition
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 is supposed 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原型应用程序设计多线程问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!