我正在尝试通过使用Windows API的EnterCriticalSection
和LeaveCriticalSection
来实现std :: cout的简单,线程安全的解决方法。
我认为示例代码是自我解释的,所以我们在这里:
#include <cstdlib>
#include <iostream>
#include <windows.h>
namespace mynamespace {
class MyStream {
public:
MyStream(void) : m_lockOwner(0) {
::InitializeCriticalSection(&m_lock);
}
~MyStream(void) {
::DeleteCriticalSection(&m_lock);
}
template <typename T>
MyStream& operator<<(const T& x) {
Lock();
std::cout << x;
return *this;
}
void Lock() {
::EnterCriticalSection(&m_lock); // Try to get the lock, irrelevant which thread it is
// One thread successfully received the lock and entered the critical section
if(m_lockOwner == ::GetCurrentThreadId()) {
// Decrease the lock count of a thread when it entered multiple times the critical section
// e.g. mynamespace::stream << "critsec1" << "critsec2"; mynamespace::stream << mynamespace::endl;
::LeaveCriticalSection(&m_lock);
}
// Store the thread ID of the thread that holds the lock at the moment
m_lockOwner = ::GetCurrentThreadId();
}
void Unlock() {
if(m_lockOwner == GetCurrentThreadId()) {
// Release the lock only if the calling thread is the owner
// Note: This should be the last decrease of the lock count
// Also reset the ownership of the lock,
// e.g. for the case that one thread is able to enter the critical section two times in a row
// mynamespace::stream << "crit first" << mynamespace::endl;
// mynamespace::stream << "crit second" << mynamespace::endl;
m_lockOwner = 0;
::LeaveCriticalSection(&m_lock);
}
}
MyStream& operator<<(MyStream& (*endl)(MyStream&)) {
return endl(*this);
}
DWORD m_lockOwner;
CRITICAL_SECTION m_lock;
};
static MyStream& endl(MyStream& stream) {
std::cout << std::endl;
stream.Unlock();
return stream;
}
MyStream stream;
};
bool alive = true;
DWORD my_thread(LPVOID t) {
while(alive) {
mynamespace::stream << "OWN THREAD" << mynamespace::endl;
}
return 0;
}
void waitForThread(HANDLE th) {
alive = false;
// Wait for thread to finish
(void)::WaitForSingleObject(th, INFINITE);
::CloseHandle(th);
th = 0;
}
int main(void) {
HANDLE th = ::CreateThread(
NULL,
0,
reinterpret_cast<LPTHREAD_START_ROUTINE>(&my_thread),
NULL,
0,
NULL);
mynamespace::stream << "test print 1" << "test print 2";
mynamespace::stream << mynamespace::endl;
mynamespace::stream << "test print 3";
::Sleep(10);
mynamespace::stream << mynamespace::endl;
::Sleep(10);
waitForThread(th);
return EXIT_SUCCESS;
}
在我的第一篇文章中,我提到有时退出程序时会遇到死锁。
这里的问题是,当我通过一个线程进入临界区时,我没有经常调用LeaveCriticalSection
。
另一个问题是我没有正确退出子线程。
有了egurs帮助和我的小补充,这个流现在应该是线程安全的。
迎接
答案 0 :(得分:1)
您输入关键部分多次,增加其引用次数,但是当您致电endl
时释放一次,以便关键部分仍然锁定
在您的类中存储一个线程ID,以便知道该线程拥有该锁。
输入临界区并比较线程ID。等于 - >这不是第一个锁,因此您需要调用LeaveCriticalSection
,否则更新线程ID变量。
class MyStream {
...
void Lock() {
WaitForCriticalSection(&m_lock);
}
DWORD m_EventOwner;
};
修改强>
从原始答案中更改了我的解决方案的机制。
这是工作代码:
namespace mynamespace {
class MyStream {
public:
MyStream(void) {
InitializeCriticalSection(&m_lock);
}
~MyStream(void) {
DeleteCriticalSection(&m_lock);
}
template <typename T>
MyStream& operator<<(const T& x) {
Lock();
std::cout << x;
return *this;
}
void Lock() {
EnterCriticalSection(&m_lock);
if (m_eventOwner == GetCurrentThreadId()) {
LeaveCriticalSection(&m_lock);
}
}
void Unlock() {
if (m_eventOwner != GetCurrentThreadId()) {
//error!
}
LeaveCriticalSection(&m_lock);
}
MyStream& operator<<(MyStream& (*endl)(MyStream&)) {
return endl(*this);
}
DWORD m_eventOwner;
CRITICAL_SECTION m_lock;
};
static MyStream& endl(MyStream& stream) {
std::cout << std::endl;
stream.Unlock();
return stream;
}
MyStream stream;
};
答案 1 :(得分:0)
来自MSDN:
删除关键部分对象后,不要在除{之外的关键部分(例如
EnterCriticalSection
,TryEnterCriticalSection
和LeaveCriticalSection
)上操作的任何函数中引用该对象{1}}和InitializeCriticalSection
。如果您尝试这样做,可能会发生内存损坏和其他意外错误。如果关键部分在其仍然拥有时被删除,则等待已删除的关键部分的所有权的线程的状态是未定义的。
在释放临界区后,您的主线程会立即有一个小窗口,在该窗口中,它可以在子线程唤醒并尝试锁定临界区之前安全地退出。当你的主线程执行InitializeCriticalSectionAndSpinCount
时,它会大大增加孩子在主要退出时锁定关键部分的可能性。