使用pybind11在C ++中涉及多线程的问题

问题描述

对于一门课程,我必须用C ++编写一个使用对象的程序,该程序使用两个线程让两个函数相互通信:函数1将'a'添加到队列中,然后函数2弹出'a队列中的'和函数1再次添加'b',这两个函数在不同的线程中运行。幸运的是,我编写的程序可以正常工作。 现在,我想使用Pybind11从Python脚本调用C ++对象。我希望这不会造成任何问题,因为多线程发生在已经起作用的C ++代码中。不幸的是,事实并非如此,不可预测的事情开始发生。

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <random>
#include <mutex>
#include <pybind11/pybind11.h>


class Communication{
  private:
    std::mutex m1;
    std::queue<double> _q;
    double lb = 0;
    double ub = 10000;
    int _t_max;
    int _max_it;
    bool _finish = 0;
    unsigned int initial_length;

  public:
    
    Communication(int t_max = 3000,int max_it = 10){
    //Upon creating the Communication object,a queue is made and a maximum iteration length (max_it) is given. t_max is the maximum 'sleeping time' between adding a random number to the queue.
      initial_length = _q.size();
      _t_max = t_max;
      _max_it = max_it;
      std::thread t1(&Communication::produce,this);
      std::thread t2(&Communication::consume,this);
      t1.join();
      t2.join();
    }

    void produce(){ //The functions which adds a random number,between 0 and 10000,to the queue. In this process,the program 'sleeps' for a time between 0 and 3000ms.
    
      //The randomizers for the to-be-added integer,and the sleeping time

      std::uniform_real_distribution<double> unif(lb,ub);
      std::default_random_engine re;
      
      std::mt19937 mt;
      std::uniform_int_distribution<> distr(0.,_t_max);
  
  
      for(int i = 0; i < _max_it; i++){
        int n = distr(mt);
        double rnd = unif(re);
        m1.lock();
        _q.push(rnd);
        std::cout << rnd << " is added to the queue." << std::endl;
        m1.unlock();
      }
      //When all the iterations have been done,the consumer needs to be told that the program is finished,by putting _finish to true.
      _finish = true;
    }

    void consume(){//The function which pops the front element of the queue.
      while(!_finish){
        if (_q.size() > initial_length){//If an element is pushed (in void produce()),pop() it in this function.
          m1.lock();
          _q.pop();
          m1.unlock();
        }
      }
    }

};

//The module required to use the object in Python
namespace py = pybind11;

PYBIND11_MODULE(Communication,m){
  py::class_<Communication>(m,"Communication")
    .def(py::init<int,int>());
};

如果我在Python中使用此对象,那么produce()函数会继续向队列中添加元素,而consumpt()函数不再会弹出它们。而且,程序不会完成,因为while(!_ finish)永远不会返回!true,即使它应该这样做也是如此。我已经检查了是否通过(在它们之间放置cout)调用了invoke()函数中的while循环和if语句。奇怪的是,这个提示某种程度上触发了if语句一次为真,然后程序再次冻结。 我一直在阅读有关GIL的其他几个主题,当您想使用多个线程时,它们可能会引起问题。我曾尝试在多个地方添加发布和获取GIL锁,但是不幸的是,这并不能解决问题。有谁知道这里出了什么问题或在哪里可以找到问题的答案?

谢谢你。

P.s .: 这是我用来检查在python中调用C ++对象的代码

import Communication
p = Communication.Communication(300,10)

解决方法

Harry和G.M.的结合在这里解决了这个问题。确实存在争用_finish和_q.size()的竞争条件。通过将m1.lock()和m1.unlock()添加到适当的位置,进程变得互斥并导致正确的行为。非常感谢!