在两个线程中使用 C++ 堆栈

Using c++ stack with two threads

我在网上搜索过,但找不到一个很好的例子来说明我正在努力完成的事情。使用 c++,我正在为 Kodi(一个开源媒体中心应用程序)进行可视化。此可视化将数据作为 http 客户端发送到 Philips Hue 桥(一个 http 服务器),以更改灯光的颜色和其他属性以匹配音乐。我正在使用 cURL 来处理 http 请求。

使用一个线程会导致 cURL 需要时间来完成其工作并从 Philips Hue 桥(http 服务器)接收响应。这会阻止屏幕上的可视化,有时还会阻止音频输出。您可以在此处查看延迟:YouTube video of the visualization.

Kodi 中的附加组件结构方式意味着没有 "main" 功能。所以对于多线程,我想不出一个好的构造,因为网络上的大多数示例在 main 中创建一个线程,然后在 main 中加入它。目前,我正在尝试这样的事情:


我已经尝试了多种创建线程的技术,但是在 Windows 中进行调试时,它们都会导致如下错误:

First-chance exception at 0x0856335A (visualization.wavforhue.dll) in Kodi.exe: 0xC0000005: Access violation reading location 0xFEEEFEF6.
First-chance exception at 0x76CDC52F in Kodi.exe: Microsoft C++ exception: access_violation at memory location 0x003BEC5C.
First-chance exception at 0x76CDC52F in Kodi.exe: Microsoft C++ exception: access_violation at memory location 0x003BEC5C.

Run-Time Check Failure #2 - Stack around the variable '_Now' was corrupted.std::this_thread::sleep_for(std::chrono::seconds(2));

或在 start 函数完成后由 Kodi 调用的 abort();


在下面的代码中启用 curl 调用后,我收到了一个不同的错误。它发生在第二次尝试播放歌曲时。第一次尝试成功。

错误是 The ordinal 4445 could not be located in the dynamic link library LIBEAY32.dll. 这个库与 SSL 关联,我的代码没有使用它。但是,我必须以某种方式影响 Kodi 程序中的其他 curl 实例。

有趣的部分是没有 curl 调用,代码似乎可以正常运行。我想如果我能成功解决 LIBEAY32.dll 问题,这就可以标记为已解决。

这里是完整的代码(底部的 GPL 声明以提高可读性):

//--------------------------------------------------------------------------------------
#include <xbmc_vis_dll.h>
#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <curl/curl.h>
#include <string>
//------------------------------------------------------------------


//------------------------------------------------------------------
#include <atomic>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
//----------------------------------------------------------------------


// Thread initialization -------------------------------------------------
std::mutex gMutex;
std::condition_variable gThreadConditionVariable;
std::atomic<bool> gRunThread;
bool gReady;
std::thread gWorkerThread;
std::queue<int> gQueue;
// End thread initialization ---------------------------------------------


void workerThread()
{
  bool isEmpty;
  std::string json;
  // This thread comes alive when Create(), Start(), or AudioData() 
  // is invoked by the main program.
  // It runs until Destroy() or Stop() is invoked.
  while (gRunThread)
  {
    //check that an item is on the stack

    {
      std::lock_guard<std::mutex> lock(gMutex);
      isEmpty = gQueue.empty();
    }

    if (isEmpty)
    {
      //Wait until AudioData() sends data.
      std::unique_lock<std::mutex> lock(gMutex);
      gThreadConditionVariable.wait(lock, []{return gReady; });
    }
    else
    {
      std::lock_guard<std::mutex> lock(gMutex);
      int value = gQueue.front();
      gQueue.pop();
    }
    if (!isEmpty)
    {
      /*
      CURL *curl = curl_easy_init();
      CURLcode res;
      json = "{\"hue\":" + std::to_string(rand() % 60000) + "}";
      // Now specify we want to PUT data, but not using a file, so it has to be a CUSTOMREQUEST
      curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
      curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L);
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
      //curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, noop_cb);
      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.c_str());
      // Set the URL that is about to receive our POST. 
      curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.10.6/api/KodiVisWave/lights/3/state");
      // Perform the request, res will get the return code
      res = curl_easy_perform(curl);
      // always cleanup curl
      curl_easy_cleanup(curl);
      */
    }
  }
}




//-- Create -------------------------------------------------------------------
// Called on load. Addon should fully initalize or return error status
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_Create(void* hdl, void* props)
{
  if (!props)
    return ADDON_STATUS_UNKNOWN;

  gRunThread = true;
  gReady = false;

  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }

  // Must initialize libcurl before any threads are started.
  //curl_global_init(CURL_GLOBAL_ALL);

  return ADDON_STATUS_OK;
}

//-- Start --------------------------------------------------------------------
// Called when a new soundtrack is played
//-----------------------------------------------------------------------------
extern "C" void Start(int iChannels, int iSamplesPerSec, int iBitsPerSample, const char* szSongName)
{
  gRunThread = true;
  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }
}

//-- Audiodata ----------------------------------------------------------------
// Called by XBMC to pass new audio data to the vis
//-----------------------------------------------------------------------------
extern "C" void AudioData(const float* pAudioData, int iAudioDataLength, float *pFreqData, int iFreqDataLength)
{
  // Processing audio data
  if (rand() % 7 == 3)
  {
    std::lock_guard<std::mutex> lock(gMutex);
    gQueue.push(1);
  }

  gRunThread = true;
  // Check if the thread is alive yet.
  if (!gWorkerThread.joinable())
  {
    gWorkerThread = std::thread(&workerThread);
  }

  // Send the curl calls to the worker thread
  {
    std::lock_guard<std::mutex> lock(gMutex);
    gReady = true;
  }
  gThreadConditionVariable.notify_one();

}

//-- Stop ---------------------------------------------------------------------
// This dll must stop all runtime activities
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Stop()
{
  gRunThread = false;
  while (gWorkerThread.joinable())
  {
    gWorkerThread.join();
  }
}

//-- Detroy -------------------------------------------------------------------
// Do everything before unload of this add-on
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Destroy()
{
  gRunThread = false;
  while (gWorkerThread.joinable())
  {
    gWorkerThread.join();
  }
}

//-- Render -------------------------------------------------------------------
// Called once per frame. Do all rendering here.
//-----------------------------------------------------------------------------
extern "C" void Render()
{

}

//-- GetInfo ------------------------------------------------------------------
// Tell XBMC our requirements
//-----------------------------------------------------------------------------
extern "C" void GetInfo(VIS_INFO* pInfo)
{
  pInfo->bWantsFreq = false;
  pInfo->iSyncDelay = 0;
}

//-- OnAction -----------------------------------------------------------------
// Handle XBMC actions such as next preset, lock preset, album art changed etc
//-----------------------------------------------------------------------------
extern "C" bool OnAction(long flags, const void *param)
{
  bool ret = false;
  return ret;
}

//-- GetPresets ---------------------------------------------------------------
// Return a list of presets to XBMC for display
//-----------------------------------------------------------------------------
extern "C" unsigned int GetPresets(char ***presets)
{
  return 0;
}

//-- GetPreset ----------------------------------------------------------------
// Return the index of the current playing preset
//-----------------------------------------------------------------------------
extern "C" unsigned GetPreset()
{
  return 0;
}

//-- IsLocked -----------------------------------------------------------------
// Returns true if this add-on use settings
//-----------------------------------------------------------------------------
extern "C" bool IsLocked()
{
  return false;
}

//-- GetSubModules ------------------------------------------------------------
// Return any sub modules supported by this vis
//-----------------------------------------------------------------------------
extern "C" unsigned int GetSubModules(char ***names)
{
  return 0; // this vis supports 0 sub modules
}

//-- HasSettings --------------------------------------------------------------
// Returns true if this add-on use settings
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" bool ADDON_HasSettings()
{
  return false;
}

//-- GetStatus ---------------------------------------------------------------
// Returns the current Status of this visualization
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_GetStatus()
{
  return ADDON_STATUS_OK;
}

//-- GetSettings --------------------------------------------------------------
// Return the settings for XBMC to display
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" unsigned int ADDON_GetSettings(ADDON_StructSetting ***sSet)
{
  return 0;
}

//-- FreeSettings --------------------------------------------------------------
// Free the settings struct passed from XBMC
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------

extern "C" void ADDON_FreeSettings()
{
}

//-- SetSetting ---------------------------------------------------------------
// Set a specific Setting value (called from XBMC)
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" ADDON_STATUS ADDON_SetSetting(const char *strSetting, const void* value)
{
  return ADDON_STATUS_OK;
}

//-- Announce -----------------------------------------------------------------
// Receive announcements from XBMC
// !!! Add-on master function !!!
//-----------------------------------------------------------------------------
extern "C" void ADDON_Announce(const char *flag, const char *sender, const char *message, const void *data)
{
}

/*
 *      Copyright (C) 2008-2016 Team Kodi
 *      http://kodi.tv
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

有更好的方法吗?如果不是,我在 destroy 函数中要做什么来减轻访问冲突?


Here is a link to an extremely bare-bones version of the source code without the complicated sound data processing, graphics rendering, and curl calls. 它仍然失败 Run-Time Check Failure #2 - Stack around the variable '_Now' was corrupted.std::this_thread::sleep_for(std::chrono::seconds(2));


编辑: 上面链接的代码现在应该可以工作了。我在 Windows 7 和 8.1 上仍然遇到 DirectX 11 和 cURL 的不同问题,但 OpenELEC、Ubuntu 和 Android 对这种构造感到满意。完整的实现在视频中链接或切换到 GitHub.

中的 master 分支

//do what here to ensure the thread ends??

看来这里正确的做法是 join 线程。显然,根据代码当前的结构方式,您无权访问 thread 对象。

void workerThread()
{
    CurlData workerCurlData;
    while (true)
    {
        ....
    }
}

在这里,我想你会想要用一个可以初始化为 true 的变量(例如,可能是 std::atomic<bool> 类型)替换硬编码的 true 并且会当需要关闭线程时设置为false

负责关闭线程的函数会赋值false,然后调用join等待线程完成执行。


注:

  • workerThread 函数的 while 循环开始时,似乎有一个不受 mutex(即 if (putStack.empty()))保护的调用
  • 您可能还想研究 std::lock_guard 以安全地处理锁定和解锁 std::mutex
  • 您可能希望用适当的条件变量替换硬编码的休眠(即 std::condition_variable 让线程休眠直到它需要再次开始工作(即,而不是使用轮询方法)

示例代码

#include <atomic>
#include <chrono>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>

std::mutex gMutex;
std::queue<int> gQueue;
std::atomic<bool> gRunThread(true);
std::thread gWorkerThread;

void workerThread();

void driver();

void start();

void addData(int i);

void end();

int main()
{
    std::thread driverThread(&driver);
    driverThread.join();

    return 0;
}

void driver()
{
    std::cout << "Starting ...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    start();

    std::this_thread::sleep_for(std::chrono::seconds(1));
    for (auto i = 0; i < 5; ++i)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        addData(i);
    }

    std::cout << "Ending ...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    end();
}

void workerThread()
{
    while (gRunThread)
    {
        bool isEmpty;

        {
            std::lock_guard<std::mutex> lock(gMutex);
            isEmpty = gQueue.empty();
        }

        if (isEmpty)
        {
            std::cout << "Waiting for the queue to fill ...\n";
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
        else
        {
            std::lock_guard<std::mutex> lock(gMutex);

            int value = gQueue.front();
            gQueue.pop();

            std::cout << "Dequeued: " << value << "\n";
        }
    }
}

void start()
{
    gWorkerThread = std::thread(&workerThread);
}

void addData(int i)
{
    {
        std::lock_guard<std::mutex> lock(gMutex);
        gQueue.push(i);
    }

    std::cout << "Queued: " << i << "\n";
}

void end()
{
    gRunThread = false;
    gWorkerThread.join();
}

示例代码输出

Starting ...
Waiting for the queue to fill ...
Waiting for the queue to fill ...
Queued: 0
Queued: 1
Dequeued: 0
Dequeued: 1
Waiting for the queue to fill ...
Queued: 2
Queued: 3
Dequeued: 2
Dequeued: 3
Waiting for the queue to fill ...
Queued: 4
Ending ...

Live Example


编辑

从评论中我现在了解到您的代码位于动态加载和卸载的库中。卸载库将导致破坏全局线程对象。因此,需要处理任何将卸载库的操作,以确保线程正确关闭。