如何并行创建并运行多个Drake Solver实例?
问题:多线程/多进程中运行多个Drake Solver实例的实现方案
我希望在多线程乃至多进程中运行多个solver。目前我通过for循环创建线程,代码如下:
for (...) { pthread_t pid; Args args; args.solver = solver??? pthread_create(&pid, NULL, &func, (void*)&args); }
在定义solver时,我尝试了多种方法,但均未成功:
- 首先,调用
const auto solver = drake::solvers::MakeSolver(solver_id);后将solver.get()传入每个线程的args。代码可编译运行,但在drake::solvers::SnoptSolver::DoSolve中出现terminate called recursively错误。发现MakeSolver返回指向kKnownSolvers中单个solver实例的unique_ptr,推测是多线程调用同一实例的DoSolve导致了该问题。 - 随后尝试创建多个solver实例:调用
StaticSolverInterface::Make<SnoptSolver>()失败,因为它定义在未命名命名空间中,仅能在该文件内访问;调用const auto solver = drake::solvers::MakeSolver(solver_id);并复制solver指向的SolverInterface也不可行,因为SolverInterface不可移动或复制。
请问我的需求是否可行?若可行,该如何实现?
解决方案
你的需求完全可行,核心是为每个线程/进程创建独立的Solver实例,避免并发访问同一实例引发的线程安全问题。以下是具体实现方案:
1. 线程内创建专属Solver实例(推荐)
不要在主线程创建单个Solver后共享给所有线程,而是在每个线程的入口函数内部调用drake::solvers::MakeSolver(solver_id),为线程生成专属的Solver实例。这样每个线程拥有独立的Solver状态,不会出现并发冲突。
示例代码调整如下:
// 线程入口函数 void* func(void* arg) { // 在线程内部创建专属的Solver实例 const auto solver = drake::solvers::MakeSolver(solver_id); // 使用solver执行求解任务 const auto result = solver->DoSolve(...); // ... 后续处理逻辑 pthread_exit(NULL); } // 主线程创建线程 for (...) { pthread_t pid; pthread_create(&pid, NULL, &func, NULL); // 若需传递任务参数,可将参数分配在堆上,避免栈内存被回收 }
2. 主线程创建多实例并转移所有权
如果需要在线程启动前准备好Solver实例,可以为每个线程单独调用MakeSolver,然后通过unique_ptr转移所有权到线程中(注意SolverInterface不可复制,必须用所有权转移)。
示例代码:
// 定义带所有权转移的参数结构体 struct Args { std::unique_ptr<drake::solvers::SolverInterface> solver; // 可添加其他任务参数 }; void* func(void* arg) { // 接管参数的所有权 auto args = std::unique_ptr<Args>(static_cast<Args*>(arg)); auto& solver = *args->solver; // 执行求解任务 const auto result = solver.DoSolve(...); // ... 后续处理 pthread_exit(NULL); } // 主线程逻辑 for (...) { pthread_t pid; auto args = std::make_unique<Args>(); // 为当前线程创建独立的Solver实例 args->solver = drake::solvers::MakeSolver(solver_id); // 转移参数所有权到线程,避免主线程回收内存 pthread_create(&pid, NULL, &func, args.release()); }
3. 多进程场景的实现
多进程场景下,每个进程拥有独立的内存空间,直接在每个进程内调用MakeSolver创建实例即可,无需考虑跨进程的共享问题,实现逻辑比多线程更简单。
失败原因说明
- 共享同一Solver实例:Drake的Solver实现(如SnoptSolver)大多不是线程安全的,多线程并发调用同一实例的
DoSolve会破坏内部状态,触发递归终止错误。 - 无法复制
SolverInterface:该接口设计为不可复制、不可移动,目的是避免状态拷贝带来的一致性问题,因此必须通过所有权转移或线程内创建的方式使用。
内容的提问来源于stack exchange,提问作者alvin
相关产品推荐
相关产品推荐

