C ++ 11原型应用程序设计多线程问题 [英] C++11 prototype app design multithreading issue

查看:142
本文介绍了C ++ 11原型应用程序设计多线程问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经设计了一个具有以下类的原型应用程序:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆