多线程程序中可能的竞争条件/未定义的行为

时间:2019-01-11 11:23:49

标签: c++ c++11

我编写了一个多线程测试程序来测试我的数据库服务器,但似乎我遇到了某种竞争条件/未定义行为的情况。

我的程序使用'n'个线程数在数据库中输入'x'个记录(IMSI)。在线程中,我获取IMSI的值(将在DB中输入),然后调用将IMSI插入DB的API。虽然我在“插入” API中没有收到任何错误,但是,并非所有IMSI都已插入数据库中!

这是程序:

 #include"DB.hpp"
 #include<thread>
 #include<vector>
 #include<string>
 #include<mutex>        
 #include<algorithm>
 #include<iostream>
 using namespace std;

 std::mutex mtx_imsi;
 std::mutex mtx_errorCount;
 std::mutex mtx_addImsi;

 class data
 {
    public:
    static int64_t imsi; //This is stored in the DB
    static int64_t no_of_threads;
    static int64_t no_of_subscribers; //No. of Imsis that will be stored.
    static int64_t error_count; //No. of IMSIs which couldn't be written.
    static vector<string> imsi_list;

    static void get_imsi(int64_t &l_imsi)
    {
        std::lock_guard<mutex> lg(mtx_imsi);

        if(imsi==405862999999999+no_of_subscribers)
           l_imsi=-1;           
        else
           l_imsi=++imsi;
    }

    static void add_count(int64_t l_count)
    {
       std::lock_guard<mutex> lg(mtx_errorCount);
       error_count+=l_count;
    }

    static void add_imsi(vector<string>& list)
    {
       std::lock_guard<mutex> lg(mtx_addImsi);
       for(const auto& x:list)
           imsi_list.push_back(x);
    }
};

int64_t data::imsi(405862999999999); //This is the initial value
int64_t data::no_of_threads;
int64_t data::no_of_subscribers;
int64_t data::error_count=0;
vector<string> data::imsi_list;

int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        cout<<endl<<"Error in input parameters"<<endl;
        cout<<endl<<argv[0]<<"[No_of_threads]   [No_of_subscribers]  [NODE_IP]"<<endl;
        cout<<"e.g. "<<argv[0]<<"10 200000 10.32.129.66"<<endl;
        exit(-1);
    }

    data::no_of_threads=stoi(argv[1]);
    data::no_of_subscribers=stoi(argv[2]);

    DB::InitDBConnection(argv[3]);   //This will initialise the DB connection with the IP

    vector<thread> t;

    for(int i=0;i<data::no_of_threads;i++)
    {
        thread th([&]{
            int64_t errorCount=0,temp_imsi; 
            vector<string> temp_list;

            data::get_imsi(temp_imsi);

            while(temp_imsi!=-1)
            {
                string l_imsi=to_string(temp_imsi);                                             
                temp_list.push_back(l_imsi);

                ReturnCode status=DB::rtInsertImsi(l_imsi);

                if(status!=INSERT_OK)
                    ++errorCount;

                data::get_imsi(temp_imsi);
            }

            data::add_count(errorCount);
            data::add_imsi(temp_list);

        });

        t.push_back(move(th));        
    }

    for(auto &x:t)
    x.join();

    std::sort (data::imsi_list.begin(), data::imsi_list.end());
    cout<<endl<<"IMSI LIST"<<endl;

    // Printing the IMSIs which were entered.
    for(const auto&x:data::imsi_list)
        cout<<x<<endl;

    cout<<endl<<"Number of Imsis used: "<<data::imsi-405862999999999;
    cout<<endl<<"Number of errors: "<<data::error_count;

    return 0;
}

此刻,我相信我的“插入”功能(在线程内部调用)没有问题,因为它已在其他没有这种行为的多线程程序中使用。未插入某些IMSI的原因可能是什么?这个主程序有什么问题吗?


在发布此问题时,我修改了实际代码以使代码更易于理解(我不知道我会删除包含该错误的行)。现在,我意识到了自己的错误。在我的实际代码中,我没有将从get_imsi()获得的Imsi传递给我的insert函数(这本来是线程安全的),而是使用获得的值填充数据结构并将该数据结构传递给insert功能。因为我没有以线程安全的方式填充数据结构,所以我得到了我提到的观察结果。

我想删除问题,但由于悬赏不断,我不能再这样做了!


2 个答案:

答案 0 :(得分:4)

我看不到您使用的是哪个编译器,但是在Linux和Mac上使用Clang和GCC时,您可以选择激活消毒器。

查看评论,我建议创建不同类型的构建以检查不同类型的问题。 -fsanitize=thread将激活线程清理器。如果您测试程序,它应该告知您最低级别的比赛条件。当他们没有真正触发运行时,它甚至会报告它们。真的有用!

当被触发时,它为您提供访问和线程启动的调用堆栈。以及变量有关。

解决此问题的一个简单方法是将变量设为原子,但是要注意,这不能解决逻辑问题。因此,请仔细观察实际情况,并最终解决更大的问题。

其他消毒剂包括检查未定义的行为,错误的内存访问...

答案 1 :(得分:3)

按照编写的程序,这里有很多问题。但是种族条件和其他类似线程的烦恼不在其中

公共静态类变量是一个非常糟糕的主意。它们基本上只是范围内的全局变量。而且大多数它们在初始化后不会改变。它们应该是const成员变量。

也许如果您在一开始就对程序进行了更加仔细和整洁的设计,那么您以后就不会发现这么麻烦的错误。

这是本可以写得更好的方式。我忠实地复制了您所做的大部分工作。我对您在做什么方面做得更好的事情还不了解:

//#include "DB.hpp"
#include <thread>
#include <vector>
#include <string>
#include <mutex>
#include <algorithm>
#include <iostream>
#include <atomic>

using namespace std;

class data
{
 public:
   data(int64_t no_of_threads, int64_t no_of_subscribers, int64_t starting_imsi)
        : no_of_threads_(no_of_threads), no_of_subscribers_(no_of_subscribers),
          starting_imsi_(starting_imsi),
          error_count_(0),
          cur_imsi_(0)
   {
      cur_imsi_ = starting_imsi;
   }

   int64_t next_imsi() {
      if ((cur_imsi_ - starting_imsi_) >= no_of_subscribers_) {
         return -1;
      } else {
         return ++cur_imsi_;
      }
   }

   void add_errors(int64_t l_count)
   {
      lock_guard<mutex> lg(mtx_error_count_);
      error_count_ += l_count;
   }

   void add_imsi_list(vector<string> const & list)
   {
      lock_guard<mutex> lg(mtx_imsi_list_);
      imsi_list_.insert(imsi_list_.end(), list.begin(), list.end());
   }

   void sort_imsi_list()
   {
      // Probably not necessary, but to be thorough.
      lock_guard<mutex> lg(mtx_imsi_list_);
      sort(imsi_list_.begin(), imsi_list_.end());
   }

   int64_t imsis_used() const { return cur_imsi_ - starting_imsi_; }

   int64_t error_count() const {
      lock_guard<mutex> lg(mtx_error_count_);
      return error_count_;
   }

   int64_t thread_count() const { return no_of_threads_; }

   vector<string> const &get_imsi_list() const { return imsi_list_; }

 private:
   const int64_t no_of_threads_;
   const int64_t no_of_subscribers_;
   const int64_t starting_imsi_;
   atomic<int64_t> cur_imsi_;

   mutable mutex mtx_error_count_; // Never const
   int64_t error_count_; //No. of IMSIs which couldn't be written.
   mutable mutex mtx_imsi_list_; // Never const
   vector<string> imsi_list_;
};


int main(int argc, char* argv[])
{
   if (argc != 3)
   {
      cout << endl << "Error in input parameters" << endl;
      cout << endl << argv[0]
           << "[No_of_threads]   [No_of_subscribers]  [NODE_IP]" << endl;
      cout << "e.g. " << argv[0] << "10 200000 10.32.129.66" << endl;
      return 1;
   }

   data imsi_generator(stoi(argv[1]), stoi(argv[2]), 405862999999999);

   // DB::InitDBConnection(argv[3]);   //This will initialise the DB connection with the IP

   vector<thread> t;

   for(int i=0;i<imsi_generator.thread_count();i++)
   {
      t.emplace_back([&imsi_generator]
                     {
                        int64_t errorCount = 0, temp_imsi;
                        vector<string> temp_list;

                        temp_imsi = imsi_generator.next_imsi();

                        while (temp_imsi != -1)
                        {
                           string const l_imsi = to_string(temp_imsi);
                           temp_list.push_back(l_imsi);

                           // ReturnCode status = DB::rtInsertImsi(l_imsi);
                           //
                           // if (status != INSERT_OK)
                           //    ++errorCount;

                           temp_imsi = imsi_generator.next_imsi();
                        }

                        imsi_generator.add_errors(errorCount);
                        imsi_generator.add_imsi_list(temp_list);
                     });
   }

   for (auto &x : t)
      x.join();

   imsi_generator.sort_imsi_list();
   cout << endl << "IMSI LIST" << endl;

   // Printing the IMSIs which were entered.
   for (auto const &x : imsi_generator.get_imsi_list())
      cout << x << endl;

   cout << endl << "Number of Imsis used: " << imsi_generator.imsis_used();
   cout << endl << "Number of errors: " << imsi_generator.error_count();

   return 0;
}