c ++多线程输出在join()上冻结
c++ Multithreaded output freezes on join()
这是我为输出到文件、数据库等的输出驱动程序编写的代码模型。
在 main 的数组中,如果我有两个相同子类型的对象,代码会在第二次调用 ShutDown()
时停止。但是,如果我有两个不同子类型的对象,它不会停止,正确退出程序。我不知道是什么导致了这个问题。
#include <iostream>
#include <fstream>
#include <list>
#include <thread>
#include <condition_variable>
using namespace std;
class Parent
{
public:
Parent();
virtual ~Parent() = default;
virtual void ShutDown() = 0;
void Push(int aInt);
virtual void Write(int aInt) = 0;
bool IsEmpty() { return mList.empty(); }
protected:
std::list<int> Pop();
void WriteWithThread();
std::list<int> mList;
std::thread mOutputThread;
std::condition_variable mCV;
std::mutex mMutex;
std::atomic_bool mProgramRunning{ true };
std::atomic_bool mThreadRunning{ false };
};
Parent::Parent()
{
mOutputThread = std::move(std::thread(&Parent::WriteWithThread, this));
}
void Parent::Push(int aInt)
{
std::unique_lock<std::mutex> lock(mMutex);
mList.emplace_back(std::move(aInt));
lock.unlock();
mCV.notify_one();
}
std::list<int> Parent::Pop()
{
std::unique_lock<std::mutex> lock(mMutex);
mCV.wait(lock, [&] {return !mList.empty(); });
std::list<int> removed;
removed.splice(removed.begin(), mList, mList.begin());
return removed;
}
void Parent::WriteWithThread()
{
mThreadRunning = true;
while (mProgramRunning || !mList.empty())
{
Write(Pop().front());
}
mThreadRunning = false;
}
class ChildCout : public Parent
{
public:
ChildCout() = default;
void ShutDown() override;
void Write(int aInt) override;
};
void ChildCout::ShutDown()
{
mProgramRunning = false;
if (mOutputThread.joinable())
{
mOutputThread.join();
}
std::cout << "Shutdown Complete"<< std::endl;
}
void ChildCout::Write(int aInt)
{
std::cout << "Inserting number: " << aInt << std::endl;
}
class ChildFile : public Parent
{
public:
ChildFile(std::string aFile);
void ShutDown() override;
void Write(int aInt) override;
private:
std::fstream mFS;
};
ChildFile::ChildFile(std::string aFile):Parent()
{
mFS.open(aFile);
}
void ChildFile::ShutDown()
{
mProgramRunning = false;
if (mOutputThread.joinable())
{
mOutputThread.join();
}
mFS.close();
std::cout << "Shutdown Complete" << std::endl;
}
void ChildFile::Write(int aInt)
{
mFS<< "Inserting number: " << aInt << std::endl;
}
int main()
{
Parent *array[] = {new ChildFile("DriverOutput.txt"),new ChildFile("Output2.txt"), new ChildCout()};
for (int i = 0; i < 1000; i++)
{
for (auto& child : array)
{
child->Push(i);
}
}
for (auto& child : array)
{
child->ShutDown();
}
return 0;
}
您从不通知线程有关关闭的信息,谓词甚至不考虑 mProgramRunning
是否已设置为 false。写完后需要通知条件变量mProgramRunning
你需要相应地改变谓词
例如这样的事情应该可行,但我认为 Pop
和 WriteWithThread
函数的分离不是最理想的,因为签名会强制您向 mProgramRunning
.
添加不必要的读取
...
std::list<int> Parent::Pop()
{
std::unique_lock<std::mutex> lock(mMutex);
//mCV.wait(lock, [&] {return !mList.empty(); });
bool running = true;
mCV.wait(lock, [this, &running] { // member variables accessed via this pointer -> capture this by value
running = running = mProgramRunning.load(std::memory_order_acquire);
return !running || !mList.empty(); // need to check for shutdown here too
});
std::list<int> removed;
if (running)
{
removed.splice(removed.begin(), mList, mList.begin());
}
return removed;
}
void Parent::WriteWithThread()
{
mThreadRunning = true;
while (mProgramRunning)
{
auto list = Pop();
if (list.empty())
{
break;
}
Write(list.front());
}
mThreadRunning = false;
}
...
void ChildCout::ShutDown()
{
mProgramRunning.store(false, std::memory_order_release);
mCV.notify_all(); // tell consumer about termination
if (mOutputThread.joinable())
{
mOutputThread.join();
}
std::cout << "Shutdown Complete" << std::endl;
}
...
void ChildFile::ShutDown()
{
mProgramRunning.store(false, std::memory_order_release);
mCV.notify_all(); // tell consumer about termination
if (mOutputThread.joinable())
{
mOutputThread.join();
}
mFS.close();
std::cout << "Shutdown Complete" << std::endl;
}
...
注意:我不是 100% 确定这是否完全符合您的预期,但它应该让您知道出了什么问题。
关于 std::move
的注释:这仅用于将不可分配给右值引用的内容转换为可分配给右值引用的内容。
在
mOutputThread = std::move(std::thread(&Parent::WriteWithThread, this));
没有必要使用 std::move
,因为 std::thread(&Parent::WriteWithThread, this)
是一个可以分配给右值引用的 xvalue,所以
mOutputThread = std::thread(&Parent::WriteWithThread, this);
足够了。
如果线程对象“有名称”,您需要使用 std::move
:
std::thread tempThread(&Parent::WriteWithThread, this);
mOutputThread = std::move(tempThread);
此外在
mList.emplace_back(std::move(aInt));
也没有必要使用std::move
,int
的复制和移动赋值具有相同的效果。算术类型不会从 std::move
.
的使用中受益
这是我为输出到文件、数据库等的输出驱动程序编写的代码模型。
在 main 的数组中,如果我有两个相同子类型的对象,代码会在第二次调用 ShutDown()
时停止。但是,如果我有两个不同子类型的对象,它不会停止,正确退出程序。我不知道是什么导致了这个问题。
#include <iostream>
#include <fstream>
#include <list>
#include <thread>
#include <condition_variable>
using namespace std;
class Parent
{
public:
Parent();
virtual ~Parent() = default;
virtual void ShutDown() = 0;
void Push(int aInt);
virtual void Write(int aInt) = 0;
bool IsEmpty() { return mList.empty(); }
protected:
std::list<int> Pop();
void WriteWithThread();
std::list<int> mList;
std::thread mOutputThread;
std::condition_variable mCV;
std::mutex mMutex;
std::atomic_bool mProgramRunning{ true };
std::atomic_bool mThreadRunning{ false };
};
Parent::Parent()
{
mOutputThread = std::move(std::thread(&Parent::WriteWithThread, this));
}
void Parent::Push(int aInt)
{
std::unique_lock<std::mutex> lock(mMutex);
mList.emplace_back(std::move(aInt));
lock.unlock();
mCV.notify_one();
}
std::list<int> Parent::Pop()
{
std::unique_lock<std::mutex> lock(mMutex);
mCV.wait(lock, [&] {return !mList.empty(); });
std::list<int> removed;
removed.splice(removed.begin(), mList, mList.begin());
return removed;
}
void Parent::WriteWithThread()
{
mThreadRunning = true;
while (mProgramRunning || !mList.empty())
{
Write(Pop().front());
}
mThreadRunning = false;
}
class ChildCout : public Parent
{
public:
ChildCout() = default;
void ShutDown() override;
void Write(int aInt) override;
};
void ChildCout::ShutDown()
{
mProgramRunning = false;
if (mOutputThread.joinable())
{
mOutputThread.join();
}
std::cout << "Shutdown Complete"<< std::endl;
}
void ChildCout::Write(int aInt)
{
std::cout << "Inserting number: " << aInt << std::endl;
}
class ChildFile : public Parent
{
public:
ChildFile(std::string aFile);
void ShutDown() override;
void Write(int aInt) override;
private:
std::fstream mFS;
};
ChildFile::ChildFile(std::string aFile):Parent()
{
mFS.open(aFile);
}
void ChildFile::ShutDown()
{
mProgramRunning = false;
if (mOutputThread.joinable())
{
mOutputThread.join();
}
mFS.close();
std::cout << "Shutdown Complete" << std::endl;
}
void ChildFile::Write(int aInt)
{
mFS<< "Inserting number: " << aInt << std::endl;
}
int main()
{
Parent *array[] = {new ChildFile("DriverOutput.txt"),new ChildFile("Output2.txt"), new ChildCout()};
for (int i = 0; i < 1000; i++)
{
for (auto& child : array)
{
child->Push(i);
}
}
for (auto& child : array)
{
child->ShutDown();
}
return 0;
}
您从不通知线程有关关闭的信息,谓词甚至不考虑 mProgramRunning
是否已设置为 false。写完后需要通知条件变量mProgramRunning
你需要相应地改变谓词
例如这样的事情应该可行,但我认为 Pop
和 WriteWithThread
函数的分离不是最理想的,因为签名会强制您向 mProgramRunning
.
...
std::list<int> Parent::Pop()
{
std::unique_lock<std::mutex> lock(mMutex);
//mCV.wait(lock, [&] {return !mList.empty(); });
bool running = true;
mCV.wait(lock, [this, &running] { // member variables accessed via this pointer -> capture this by value
running = running = mProgramRunning.load(std::memory_order_acquire);
return !running || !mList.empty(); // need to check for shutdown here too
});
std::list<int> removed;
if (running)
{
removed.splice(removed.begin(), mList, mList.begin());
}
return removed;
}
void Parent::WriteWithThread()
{
mThreadRunning = true;
while (mProgramRunning)
{
auto list = Pop();
if (list.empty())
{
break;
}
Write(list.front());
}
mThreadRunning = false;
}
...
void ChildCout::ShutDown()
{
mProgramRunning.store(false, std::memory_order_release);
mCV.notify_all(); // tell consumer about termination
if (mOutputThread.joinable())
{
mOutputThread.join();
}
std::cout << "Shutdown Complete" << std::endl;
}
...
void ChildFile::ShutDown()
{
mProgramRunning.store(false, std::memory_order_release);
mCV.notify_all(); // tell consumer about termination
if (mOutputThread.joinable())
{
mOutputThread.join();
}
mFS.close();
std::cout << "Shutdown Complete" << std::endl;
}
...
注意:我不是 100% 确定这是否完全符合您的预期,但它应该让您知道出了什么问题。
关于 std::move
的注释:这仅用于将不可分配给右值引用的内容转换为可分配给右值引用的内容。
在
mOutputThread = std::move(std::thread(&Parent::WriteWithThread, this));
没有必要使用 std::move
,因为 std::thread(&Parent::WriteWithThread, this)
是一个可以分配给右值引用的 xvalue,所以
mOutputThread = std::thread(&Parent::WriteWithThread, this);
足够了。
如果线程对象“有名称”,您需要使用 std::move
:
std::thread tempThread(&Parent::WriteWithThread, this);
mOutputThread = std::move(tempThread);
此外在
mList.emplace_back(std::move(aInt));
也没有必要使用std::move
,int
的复制和移动赋值具有相同的效果。算术类型不会从 std::move
.