C++ 我的第一个多线程应用程序-Boost的奇怪行为:线程?
我编写了一个多线程应用程序来解析日志文件。它基于中的互斥缓冲区示例 其思想是创建一个缓冲区类,该类具有将项目放入缓冲区并从中取出项目的函数。读线程和写线程的同步是使用条件处理的。当缓冲区未满时,新项目将写入缓冲区,当缓冲区不为空时,项目将从缓冲区中读取。否则线程将等待 该示例使用固定数量的项目进行处理,因此我将读取线程更改为在有来自文件的输入时运行,而处理线程则在有输入或缓冲区中还有项目时运行 我的问题是,如果我使用1个读取线程和1个处理线程,那么一切都是正常和稳定的。当我添加另一个处理线程时,性能有了很大的提升,即使在10.000次测试运行之后,它仍然是稳定的 现在,当我添加另一个处理器线程(1读,3处理)时,程序似乎周期性地(但不是每次)挂起(死锁?),并等待缓冲区填满或变空 为什么做同样事情的两个线程同步稳定,而其中三个线程崩溃C++ 我的第一个多线程应用程序-Boost的奇怪行为:线程?,c++,multithreading,boost,synchronization,mutex,C++,Multithreading,Boost,Synchronization,Mutex,我编写了一个多线程应用程序来解析日志文件。它基于中的互斥缓冲区示例 其思想是创建一个缓冲区类,该类具有将项目放入缓冲区并从中取出项目的函数。读线程和写线程的同步是使用条件处理的。当缓冲区未满时,新项目将写入缓冲区,当缓冲区不为空时,项目将从缓冲区中读取。否则线程将等待 该示例使用固定数量的项目进行处理,因此我将读取线程更改为在有来自文件的输入时运行,而处理线程则在有输入或缓冲区中还有项目时运行 我的问题是,如果我使用1个读取线程和1个处理线程,那么一切都是正常和稳定的。当我添加另一个处理线程时,
我是C++线程的新手,所以也许你们中有经验的程序员知道什么会导致这种行为?< 这是我的密码:
缓冲区类别:#include "StringBuffer.h"
void StringBuffer::put(string str)
{
scoped_lock lock(mutex);
if (full == BUF_SIZE)
{
{
//boost::mutex::scoped_lock lock(io_mutex);
//std::cout << "Buffer is full. Waiting..." << std::endl;
}
while (full == BUF_SIZE)
cond.wait(lock);
}
str_buffer[p] = str;
p = (p+1) % BUF_SIZE;
++full;
cond.notify_one();
}
string StringBuffer::get()
{
scoped_lock lk(mutex);
if (full == 0)
{
{
//boost::mutex::scoped_lock lock(io_mutex);
//std::cout << "Buffer is empty. Waiting..." << std::endl;
}
while (full == 0)
cond.wait(lk);
}
string test = str_buffer[c];
c = (c+1) % BUF_SIZE;
--full;
cond.notify_one();
return test;
}
#包括“StringBuffer.h”
void StringBuffer::put(字符串str)
{
作用域_锁(互斥锁);
如果(完整==基本尺寸)
{
{
//boost::mutex::作用域锁定锁(io_mutex);
//我看不出有什么问题
但是需要记住的一点是,仅仅因为线程从带有信号的条件变量中释放,并不意味着它开始运行
一旦释放,它必须在继续之前获取互斥锁,但每次线程计划运行时,可能有其他人锁定了互斥锁(考虑到这些循环有多紧,这并不奇怪),因此它会暂停等待下一个调度槽。这可能就是冲突所在
问题在于,将打印语句放入代码中不会有任何帮助,因为打印语句会影响计时(它们很昂贵)因此,您将获得不同的行为。一些便宜的东西,比如记下每个线程操作的成本可能足够低,这样就不会影响计时,但可以帮助您确定问题。注意:只在完成后打印结果。As@Martin我看不到代码中有任何明显的问题。我唯一的想法是您可以尝试使用单独的条件变量来写入缓冲区和读取缓冲区。现在的情况是,每当一个线程获取完一个项目时,在get
方法中等待的其他线程也可能会收到信号
考虑以下情况。缓冲区已满,因此写入程序等待cond
信号。现在,读卡器清空队列,写入程序甚至没有收到一次信号。这是可能的,因为它们使用相同的条件变量,并且读卡器越多,可能性就越大。每次读卡器从缓冲区中删除一个项目,它调用notify\u one
。这可以唤醒写入程序,但也可以唤醒读卡器。假设所有通知最终都会唤醒读卡器。写入程序将永远不会被释放。最后,所有线程都将等待一个信号,而您将处于死锁状态
如果这是正确的,那么您有两种可能的修复方法:
使用不同的信号防止读者“窃取”写手的通知
使用notify_all
而不是notify_one
,以确保每次删除项目时读者都有机会
我们需要看到导致问题的代码,特别是锁定。现在我们需要所有能够编译和运行代码的东西。所以需要头和适当的数据。将其归结为一个完整的实体,我们可以编译和复制行为。+1用于提及不同条件变量的使用。我虽然想到了这一点,但失败了哦,别提了。
Parser p;
StringBuffer buf;
Report report;
string transfer;
ifstream input;
vector <boost::regex> regs;
int proc_count = 0;
int push_count = 0;
bool pusher_done = false;
// Show filter configuration and init report by dimensioning counter vectors
void setup_report() {
for (int k = 0; k < p.filters(); k++) {
std::cout << "SID(NUM):" << k << " Name(TXT):\"" << p.name_at(k) << "\"" << " Filter(REG):\"" << p.filter_at(k) << "\"" << endl;
regs.push_back(boost::regex(p.filter_at(k)));
report.hits_filters.push_back(0);
report.names.push_back(p.name_at(k));
report.filters.push_back(p.filter_at(k));
}
}
// Read strings from sourcefiles and put them into buffer
void pusher() {
// as long as another string could be red, ...
while (input) {
// put it into buffer
buf.put(transfer);
// and get another string from source file
getline(input, transfer);
push_count++;
}
pusher_done = true;
}
// Get strings from buffer and check RegEx filters. Pass matches to report
void processor()
{
while (!pusher_done || buf.get_rest()) {
string n = buf.get();
for (unsigned sid = 0; sid < regs.size(); sid++) {
if (boost::regex_search(n, regs[sid])) report.report_hit(sid);
}
boost::mutex::scoped_lock lk(buf.count_mutex);
{
proc_count++;
}
}
}
int main(int argc, const char* argv[], char* envp[])
{
if (argc == 3)
{
// first add sourcefile from argv[1] filepath, ...
p.addSource(argv[1]);
std::cout << "Source File: *** Ok\n";
// then read configuration from argv[2] filepath, ...
p.readPipes(envp, argv[2]);
std::cout << "Configuration: *** Ok\n\n";
// and setup the Report Object.
setup_report();
// For all sourcefiles that have been parsed, ...
for (int i = 0; i < p.sources(); i++) {
input.close();
input.clear();
// open the sourcefile in a filestream.
input.open(p.source_at(i).c_str());
// check if file exist, otherwise throw error and exit
if (!input)
{
std::cout << "\nError! File not found: " << p.source_at(i);
exit(1);
}
// get start time
std::cout << "\n- started: ";
ptime start(second_clock::local_time());
cout << start << endl;
// read a first string into transfer to get the loops going
getline(input, transfer);
// create threads and pass a reference to functions
boost::thread push1(&pusher);
boost::thread proc1(&processor);
boost::thread proc2(&processor);
// start all the threads and wait for them to complete.
push1.join();
proc1.join();
proc2.join();
// calculate and output runtime and lines per second
ptime end(second_clock::local_time());
time_duration runtime = end - start;
std::cout << "- finished: " << ptime(second_clock::local_time()) << endl;
cout << "- processed lines: " << push_count << endl;
cout << "- runtime: " << to_simple_string(runtime) << endl;
float processed = push_count;
float lines_per_second = processed/runtime.total_seconds();
cout << "- lines per second: " << lines_per_second << endl;
// write report to file
report.create_filereport(); // after all threads finished write reported data to file
cout << "\nReport saved as: ./report.log\n\nBye!" << endl;
}
}
else std::cout << "Usage: \"./Speed-Extract [source][config]\"\n\n";
return 0;
}