代码之家  ›  专栏  ›  技术社区  ›  Vishal Sharma

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

  •  -2
  • Vishal Sharma  · 技术社区  · 6 年前

    我已经编写了一个多线程测试程序来测试我的数据库服务器,但看起来我遇到了某种竞争条件/未定义的行为场景。

    我的程序使用 'n' 要输入的线程数 'x' 数据库中的记录数(IMSIS)。在线程中,我获取imsi的值(将在db中输入),然后调用将imsi插入db的API。尽管,我在“insert”API中没有得到任何错误,但是,并不是所有imsis都被插入到数据库中!

    程序如下:

     #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;
    }
    

    此时,我相信我的“insert”函数(我在线程内部调用)没有什么问题,因为它用于其他没有这种行为的多线程程序。为什么没有插入某些imsi?这个主程序有什么问题吗?


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

    我想删除这个问题,但由于奖金不断,我不能再做了!


    2 回复  |  直到 6 年前
        1
  •  3
  •   Omnifarious    6 年前

    正如所写,这个程序有许多问题。但种族条件和其他类似的线状污秽不在其中。

    公共静态类变量是一个非常糟糕的主意。它们基本上只是范围全局变量。它们中的大多数在初始化后不会改变。他们应该是 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;
    }
    
        2
  •  4
  •   JVApen    6 年前

    我不知道您在使用哪个编译器,但是在Linux和Mac上使用clang和gcc时,您可以选择激活消毒剂。

    查看这些评论,我建议创建不同风格的构建来检查不同类型的问题。 -fsanitize=thread 将激活线程消毒剂。如果你测试你的程序,它应该告诉你关于最低水平的比赛条件。它甚至在它们在那次运行中没有触发时报告它们。真有用!

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

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

    其他消毒剂包括检查未定义的行为、不正确的内存访问…