使用线程时,C ++ app性能会有所不同

时间:2014-12-06 00:10:36

标签: c++ multithreading mutex

我有一个带2个线程的C ++应用程序。该应用程序在屏幕上显示一个仪表,其指示器根据通过UDP套接字接收的角度旋转。我的问题是指标应该以恒定的速度旋转,但它的表现有时候会慢慢减速,而且它也可以快速前进,以便在其他时间快速赶上,间歇性地暂停一些。

每个帧,显示(主)线程保护UDP线程的角度副本。 UDP线程还保护写入共享变量。我使用Windows CriticalSection对象来保护'通信'线程之间。以与显示更新大致相同的速率接收UDP分组。我使用的是Windows 7,64位,带有4核处理器。

我使用单独的python应用程序来广播UDP数据包。我使用python函数time.sleep来保持广播的恒定速率。

为什么应用程序会变慢? 为什么应用程序似乎快进而不是捕捉到最新的角度? 什么是正确的解决方案?

编辑:当应用程序似乎“快进”时,我并不是100%确定所有角度值都会被记住。该应用程序有时会抢占一些价值(不确定它是否是最新的#39;)。

编辑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 个答案:

答案 0 :(得分:2)

我知道这在假设空间中有点但是:

您所谈论的费率是在&#34;服务器&#34;中设置的。和&#34;客户&#34;通过睡眠来控制数据包的发送速度。这不一定是实际传输速率,因为操作系统可以非常不对称的方式安排您的流程(时间)。

这可能意味着当服务器获得更多时间时,它将用数据包填充OS缓冲区(客户端将获得更少的处理器时间,因此,以较低的速率消耗=&gt;减慢仪表速度)。然后,当客户端获得服务器的更多时间时,它将快速消耗所有数据包,而更新线程仍将进行一些等待。但这并不意味着它会“快速”,因为你使用一个关键部分来锁定数据包更新,所以你可能不会从OS缓冲区中消耗太多的包直到新的更新。 (你可能有一个&#34;对着&#34;但是只有一小步)。我基于这样一个事实:我看到你的接收或更新方法没有实际睡眠(唯一的睡眠是在服务器端完成的)。