c++ - 为什么并行执行 Tcl_ExprDouble 的独立 Tcl 解释器需要互斥体?

标签 c++ multithreading tcl mutex

我编写了一个简单的类,它在 Tcl 中包装了回调。它管理自己的 Tcl 解释器并将 Tcl 命令存储为字符串。 go 方法将字符串提供给解释器并返回结果。

#include <iostream>
#include <string>
#include <vector>
#include <future>
#include <tcl.h>

class Tcl_callback {
  std::string callback;
  Tcl_Interp *local_interp;

 public:  
  Tcl_callback(std::string c): 
    callback(std::move(c)),
    local_interp(Tcl_CreateInterp()) 
    {}

  ~Tcl_callback() {Tcl_DeleteInterp(local_interp);}

  Tcl_callback(const Tcl_callback & c):
    callback(c.callback),
    local_interp(Tcl_CreateInterp()) {}

  double go() {
    std::cout << "going..." << std::endl;
    double resultValue;
    int resultCode;
    resultCode = Tcl_ExprDouble(local_interp, callback.c_str(), &resultValue);
    if (resultCode != TCL_OK) {
      throw std::runtime_error("ERROR: failed evaluation of the expression: \"" + callback + "\"\n  " + Tcl_GetStringResult(local_interp));
    }
    return resultValue;
  }
};

我已经使用一个简单的 main 对其进行了测试,该 main 允许在并行和串行执行之间切换:

#define PARALLEL

int main() {
  const int n_callbacks = 100;
  const int n_iter = 10;

  std::vector<Tcl_callback> cs(n_callbacks, Tcl_callback("sqrt(123)"));

  for (int i = 0; i < n_iter; i++) {
  #ifdef PARALLEL
    std::vector<std::future<double>> fs;
    for (auto & c: cs) {
      fs.push_back( std::async(std::launch::async,[&](){return c.go();}) );
    }
    for (auto & f: fs) {
      std::cout << f.get() << std::endl;
    }
  #else
    for (auto & c: cs) {
      std::cout << c.go() << std::endl;
    }
  #endif
  }

  std::cout << "done" << std::endl;
  return 0;
}

尽管所有 Tcl_callback 对象看起来都是独立的,但如果不使用全局互斥体保护 go 方法,我无法获得稳定的并行版本:

std::mutex m; //at global scope
std::lock_guard<std::mutex> lk(m); //inside the go method

我想了解其原因以及改进代码的可能方法。

最佳答案

问题是 Tcl_Interp 只能从创建它的线程访问(例如,通过 Tcl_ExprDoubleTcl_DeleteInterp); Tcl 解释器的实现大量使用线程局部变量以避免持有全局锁。不幸的是,您在启动所有线程之前创建解释器,导致解释器跨线程运行,这是行不通的。

来自documentation ...

The token returned by Tcl_CreateInterp may only be passed to Tcl routines called from the same thread as the original Tcl_CreateInterp call. It is not safe for multiple threads to pass the same token to Tcl's routines.

将代码更改为此(其中 Tcl_Interpgo 方法的范围内是本地的):

class Tcl_callback {
  std::string callback;

 public:
  Tcl_callback(std::string c):
    callback(std::move(c))
    {}

  ~Tcl_callback() {}

  Tcl_callback(const Tcl_callback & c):
    callback(c.callback) {}

  double go() {
    std::cout << "going..." << std::endl;
    double resultValue;
    int resultCode;
    Tcl_Interp *interp = Tcl_CreateInterp();
    resultCode = Tcl_ExprDouble(interp, callback.c_str(), &resultValue);
    if (resultCode != TCL_OK) {
      throw std::runtime_error("ERROR: failed evaluation of the expression: \"" + callback + "\"\n  " + Tcl_GetStringResult(interp));
    }
    Tcl_DeleteInterp(interp);
    return resultValue;
  }
};

哪个效率较低会使其工作(或者至少当我尝试时它工作)。我将让您弄清楚如何使用聪明的方法来避免创建相当这么多的解释器! (我还将让您清理抛出异常情况下潜在的资源泄漏。这只是概念验证代码。)

关于c++ - 为什么并行执行 Tcl_ExprDouble 的独立 Tcl 解释器需要互斥体?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29648206/

相关文章:

windows - TCL : Execute a windows command line and see the "flow" of the application

C++:使用拆分解析日志,但一个条目可以有几行

c++ - std::async 与共享指针模板化成员函数

c# - 这是一种安全且*相对可行*的异步记录某些事件的方法吗?

tcl - 读取事件处理程序中的所有输入?

shell - 如何从shell脚本运行TCL脚本?

c++ - 将 ifstream 读入字符串的最有效方法是什么?

C++:条件表达式中的 Always-Throw 函数

c++ - VS2010编译错误。 stdio.h(378) : fatal error C1003: error count exceeds 100; stopping compilation

android - TextView setText 不刷新文本