2014-12-06 4 views
2

У меня есть приложение C++ с 2 потоками. Приложение отображает датчик на экране с индикатором, который вращается на основе угла, полученного через сокет UDP. Моя проблема заключается в том, что индикатор должен вращаться с постоянной скоростью, но он ведет себя, как время замедляет время от времени, а также быстро вперед, чтобы быстро догнать в другое время, с некоторыми паузами с перерывами.Производительность приложения C++ зависит от использования потоков

Каждый кадр, основная (основная) резьба защищает копию угла от нити UDP. Поток UDP также защищает запись общей переменной. Я использую объект Windows CriticalSection для защиты «связи» между потоками. Пакет UDP принимается примерно с той же скоростью, что и обновление дисплея. Я использую Windows 7, 64 бит, с 4-ядерным процессором.

Я использую отдельное приложение python для трансляции UDP-пакета. Я использую функцию python, time.sleep, чтобы поддерживать широковещательную передачу с постоянной скоростью.

Почему приложение замедляется? Почему приложение кажется ускоренным, вместо привязки к последнему углу? Какое правильное исправление?

EDIT: Я не уверен на 100%, что все значения угла запоминаются, когда приложение кажется «вперед». Приложение мгновенно привязывается к некоторому значению (не уверен, является ли оно «последним»).

EDIT 2: по запросу, некоторый код.

void App::udp_update(DWORD thread_id) 
{ 
    Packet p; 
    _socket.recv(p); // edit: blocks until transmission is received 

    { 
    Locker lock(_cs); 
    _packet = p; 
    } 
} 

void App::main_update() 
{ 
    float angle_copy = 0.f; 

    { 
    Locker lock(_cs); 
    angle_copy = _packet.angle; 
    } 

    draw(angle_copy); // edit: blocks until monitor refreshes 
} 

Thread.h

class CS 
{ 
private: 
    friend Locker; 

    CRITICAL_SECTION _handle; 

    void _lock(); 
    void _unlock(); 

    // not implemented by design 
    CS(CS&); 
    CS& operator=(const CS&); 

public: 
    CS(); 
    ~CS(); 
}; 


class Locker 
{ 
private: 
    CS& _cs; 

    // not implemented by design 
    Locker(); 
    Locker(const Locker&); 
    Locker& operator=(const Locker&); 

public: 
    Locker(CS& c) 
    : _cs(c) 
    { 
    _cs._lock(); 
    } 

    ~Locker() 
    { 
    _cs._unlock(); 
    } 
}; 



class Win32ThreadPolicy 
{ 
public: 
    typedef Functor<void,TYPELIST_1(DWORD)> Callback; 

private: 
    Callback _callback; 

    //SECURITY_DESCRIPTOR _sec_descr; 
    //SECURITY_ATTRIBUTES _sec_attrib; 
    HANDLE _handle; 
    //DWORD _exitValue; 

#ifdef USE_BEGIN_API 
    unsigned int _id; 
#else // USE_BEGIN_API 
    DWORD _id; 
#endif // USE_BEGIN_API 

    /*volatile*/ bool _is_joined; 

#ifdef USE_BEGIN_API 
    static unsigned int WINAPI ThreadProc(void* lpParameter); 
#else // USE_BEGIN_API 
    static DWORD WINAPI ThreadProc(LPVOID lpParameter); 
#endif // USE_BEGIN_API 

    DWORD _run(); 
    void _join(); 

    // not implemented by design 
    Win32ThreadPolicy(); 
    Win32ThreadPolicy(const Win32ThreadPolicy&); 
    Win32ThreadPolicy& operator=(const Win32ThreadPolicy&); 

public: 
    Win32ThreadPolicy(Callback& func); 
    ~Win32ThreadPolicy(); 

    void Spawn(); 
    void Join(); 
}; 

/// helps to manage parallel operations. 
/// attempts to mimic the C++11 std::thread interface, but also passes the thread ID. 
class Thread 
{ 
public: 
    typedef Functor<void,TYPELIST_1(DWORD)> Callback; 
    typedef Win32ThreadPolicy PlatformPolicy; 

private: 
    PlatformPolicy _platform; 

    /// not implemented by design 
    Thread(); 
    Thread(const Thread&); 
    Thread& operator=(const Thread&); 

public: 
    /// begins parallel execution of the parameter, func. 
    /// \param func, the function object to be executed. 
    Thread(Callback& func) 
    : _platform(func) 
    { 
    _platform.Spawn(); 
    } 

    /// stops parallel execution and joins with main thread. 
    ~Thread() 
    { 
    _platform.Join(); 
    } 
}; 

Thread.cpp

#include "Thread.h" 

void CS::_lock() 
{ 
    ::EnterCriticalSection(&_handle); 
} 

void CS::_unlock() 
{ 
    ::LeaveCriticalSection(&_handle); 
} 

CS::CS() 
    : _handle() 
{ 
    ::memset(&_handle, 0, sizeof(CRITICAL_SECTION)); 

    ::InitializeCriticalSection(&_handle); 
} 

CS::~CS() 
{ 
    ::DeleteCriticalSection(&_handle); 
} 


Win32ThreadPolicy::Win32ThreadPolicy(Callback& func) 
    : _handle(NULL) 
    //, _sec_descr() 
    //, _sec_attrib() 
    , _id(0) 
    , _is_joined(true) 
    , _callback(func) 
{ 
} 

void Win32ThreadPolicy::Spawn() 
{ 
    // for an example of managing descriptors, see: 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa446595%28v=vs.85%29.aspx 
    //BOOL success_descr = ::InitializeSecurityDescriptor(&_sec_descr, SECURITY_DESCRIPTOR_REVISION); 

    //TODO: do we want to start with CREATE_SUSPENDED ? 

    // TODO: wrap this with exception handling 
#ifdef USE_BEGIN_END 
    // http://msdn.microsoft.com/en-us/library/kdzttdcb%28v=vs.100%29.aspx 
    _handle = (HANDLE) _beginthreadex(NULL, 0, &Thread::ThreadProc, this, 0, &_id); 
#else // USE_BEGIN_END 
    _handle = ::CreateThread(NULL, 0, &Win32ThreadPolicy::ThreadProc, this, 0, &_id); 
#endif // USE_BEGIN_END 
} 

void Win32ThreadPolicy::_join() 
{ 
    // signal that the thread should complete 
    _is_joined = true; 

    // maybe ::WFSO is not the best solution. 
    // "Except that WaitForSingleObject and its big brother WaitForMultipleObjects are dangerous. 
    // The basic problem is that these calls can cause deadlocks, 
    // if you ever call them from a thread that has its own message loop and windows." 
    // http://marc.durdin.net/2012/08/waitforsingleobject-why-you-should-never-use-it/ 
    // 
    // He advises to use MsgWaitForMultipleObjects instead: 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms684242%28v=vs.85%29.aspx 
    DWORD result = ::WaitForSingleObject(_handle, INFINITE); 

    // _handle must have THREAD_QUERY_INFORMATION security access enabled to use the following: 
    //DWORD exitCode = 0; 
    //BOOL success = ::GetExitCodeThread(_handle, &_exitValue); 
} 

Win32ThreadPolicy::~Win32ThreadPolicy() 
{ 
} 

void Win32ThreadPolicy::Join() 
{ 
    if(!_is_joined) 
    { 
     _join(); 
    } 

    // this example shows that it is correct to pass the handle returned by CreateThread 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682516%28v=vs.85%29.aspx 
    ::CloseHandle(_handle); 

    _handle = NULL; 
} 

DWORD Win32ThreadPolicy::_run() 
{ 
    // TODO: do we need to make sure _id has been assigned? 

    while(!_is_joined) 
    { 
     _callback(_id); 
     ::Sleep(0); 
    } 

    // TODO: what should we return? 
    return 0; 
} 

#ifdef USE_BEGIN_END 
unsigned int WINAPI Thread::ThreadProc(LPVOID lpParameter) 
#else // USE_BEGIN_END 
    DWORD WINAPI Win32ThreadPolicy::ThreadProc(LPVOID lpParameter) 
#endif // USE_BEGIN_END 
{ 
    Win32ThreadPolicy* tptr = static_cast<Win32ThreadPolicy*>(lpParameter); 
    tptr->_is_joined = false; 

    // when this function (ThreadProc) returns, ::ExitThread is used to terminate the thread with an "implicit" call. 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682453%28v=vs.85%29.aspx 
    return tptr->_run(); 
} 
+1

Затруднились ответить, посмотрев фактический код. Однако это звучит так, будто что-то блокирует поток рендеринга. Я попытался охватить проблему, выполнив некоторые профилирования в ваших потоках. Также я прочитал, что вы получаете угол через UDP-сокет? Как вы достигаете постоянной скорости вращения? Я ожидаю, что битрейт не всегда будет постоянным. – Gio

+0

Код темы и защитный код: http://pastebin.com/LrDcSQKS http://pastebin.com/MLDbNTjk – cyrf

+0

укажите, пожалуйста, как можно больше кода в свой вопрос! – didierc

ответ

2

Я знаю, что это немного в предположении пространстве, но:

Скорость вы говорят о том, что установлен на сервере »и« клиент »через спящий режим, который контролирует скорость отправки пакетов. Это не обязательно скорость фактической передачи, так как ОС может планировать ваши процессы очень асимметричным образом (по времени).

Это может означать, что когда сервер получает больше времени, он заполняет буфер ОС пакетами (клиент будет получать меньше процессорного времени, таким образом, достигнув более низкой скорости => замедляя счетчик). Затем, когда клиент получает больше времени на сервер, он будет быстро потреблять все пакеты, а поток обновлений по-прежнему будет ждать. Но это не означает, что он будет «привязываться», потому что вы используете критический раздел для блокировки обновления пакета, поэтому, вероятно, вы не получите слишком много пакетов из буфера ОС до нового обновления. (у вас может быть «привязка к», но с небольшим шагом). Я основываю это на том факте, что я не вижу фактического сна в методах получения или обновления (единственный сон выполняется на стороне сервера).

+0

Спасибо, ваши наблюдения, похоже, объясняют поведение. Спасибо, что посмотрели на проблему на высоком уровне. Можете ли вы предложить правильное исправление, когда ОС планирует, как это угодно? Обычно я отправляю и получаю на том же ПК, но иногда я могу использовать отдельные ПК. – cyrf

+0

Зависит от того, что вы хотите. Вам нужны все значения (и плавное движение счетчика) ?. Вам нужна верность (привязка к новейшей).Чтобы получить первый очень простой, вы можете уведомить графический поток изменений (вызвать ничью в ближайшем будущем), чтобы каждый пакет получал «визуализированный». –

+0

Еще раз спасибо. Я считаю, что мой код уже достигает «очень простого» решения, которое вы предлагаете, и я страдаю побочными эффектами. – cyrf

Смежные вопросы