我编写了一个多线程测试程序来测试我的数据库服务器,但似乎我遇到了某种竞争条件/未定义行为的情况。
我的程序使用'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功能。因为我没有以线程安全的方式填充数据结构,所以我得到了我提到的观察结果。
我想删除问题,但由于悬赏不断,我不能再这样做了!
答案 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;
}