[Threads] Much faster try_mutex on Windows NT

Hello. try_mutex uses a Mutex instead of a CRITICAL_SECTION because on Windows 9x and Me it's impossible to try the aquisition of a CRITICAL_SECTION. On Windows NT/2000/XP this is possible, using the function TryEnterCriticalSection. So I suggest this: choose at runtime what syncronization primitive to use based on the running OS. CRITICAL_SECTIONS are much faster and since Windows NT is gradually replacing Windows 9x systems I think it is good to use this feature. A sketch follows. ---------- mutex.cpp ----------- namespace { bool g_Init = false; bool g_UseTryCritical; TryCriticalProcType g_TryCritical; void TryCriticalInit() { g_Init = true; g_UseTryCritical = false; get windows version; if (!windows nt) return; lib = load library (kernel32.dll); g_TryCritical = get proc address (lib, TryEnterCriticalSection); g_UseTryCritical = true; } } try_mutex::try_mutex() { if (!g_Init) TryCriticalInit(); if (g_UseTryCritical) { try { m_mutex = reinterpret_cast<void*>(new CRITICAL_SECTION); } catch (...) { } } else { m_mutex = reinterpret_cast<void*>(CreateMutex(0, 0, 0)); } if (!m_mutex) throw thread_resource_error(); if (g_UseTryCritical) InitializeCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex)); } bool try_mutex::do_trylock() { if (g_UseTryCritical) { return g_TryCritical(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex)) != 0; } else { unsigned int res = 0; res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), 0); assert(res != WAIT_FAILED && res != WAIT_ABANDONED); return res == WAIT_OBJECT_0; } } ---------- mutex.cpp ----------- There is only one potential problem: if (!g_Init) TryCriticalInit(); might not be thread safe. Something like a thread safe singleton might be needed. Or maybe we could use InterlockedExchange & co. I'm not sure if timed_mutex could also benefit from this. In the docs you should explain what joining means. I worked a lot with threads in the past, but I've never heard of such a concept. I had to look through the source codes to understand what join means. Many Win32 developers will not know of concepts not covered by the Platform SDK. You do explain that conditions are some sort of events, but you use joining in sample code without explaining what it does. Regards, Adal Chiriliuc

Adal Chiriliuc wrote:
Hello.
try_mutex uses a Mutex instead of a CRITICAL_SECTION because on Windows 9x and Me it's impossible to try the aquisition of a CRITICAL_SECTION.
Yeah. class swap_based_mutex_for_windows { // noncopyable atomic<int> m_lock_status; // 0: free, 1: locked, -1: lock-contention auto_reset_event m_retry_event; public: // ctor/dtor [w/o lazy event init] void lock() { if (m_lock_status.swap(1, msync::acq)) while (m_lock_status.swap(-1, msync::acq)) m_retry_event.wait(); } bool trylock() { return !m_lock_status.swap(1, msync::acq) ? true : !m_lock_status.swap(-1, msync::acq); } bool timedlock(absolute_timeout const & timeout) { if (m_lock_status.swap(1, msync::acq)) { while (m_lock_status.swap(-1, msync::acq)) if (!m_retry_event.timedwait(timeout)) return false; } return true; } void unlock() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); } }; regards, alexander.

On Monday, March 22, 2004 Alexander Terekhov wrote:
Adal Chiriliuc wrote:
Hello.
try_mutex uses a Mutex instead of a CRITICAL_SECTION because on Windows 9x and Me it's impossible to try the aquisition of a CRITICAL_SECTION.
Yeah.
class swap_based_mutex_for_windows { // noncopyable
atomic<int> m_lock_status; // 0: free, 1: locked, -1: lock-contention auto_reset_event m_retry_event;
public:
...
You mean to use an atomic int and an event to simulate a mutex? Wasn't event usage discouraged because it is unsafe? atomic, auto_reset_event and msync are not defined anywhere in my Boost 1.31.0 folder (full text search). Adal Chiriliuc

David Abrahams wrote:
"Carl Daniel" <cpdaniel@pacbell.net> writes:
Adal Chiriliuc wrote:
Wasn't event usage discouraged because it is unsafe?
That was semaphores.
IIRC Windows Events have safety problems too.
I didn't know about that. Could you give a link or something, where such problems are explained? (right now I'm working no an app., and it uses a lot of Windows Events) Best, John

John Torjo wrote: [...]
Could you give a link or something, ...
http://groups.google.com/groups?selm=3BE7ABD3.6303D8E1%40web.de See also <http://thread.gmane.org/gmane.comp.lib.boost.devel/20799>. regards, alexander.

At 12:59 AM 3/24/2004, John Torjo wrote:
David Abrahams wrote:
"Carl Daniel" <cpdaniel@pacbell.net> writes:
Adal Chiriliuc wrote:
Wasn't event usage discouraged because it is unsafe?
That was semaphores.
IIRC Windows Events have safety problems too.
I didn't know about that. Could you give a link or something, where such problems are explained? (right now I'm working no an app., and it uses a lot of Windows Events)
Semaphores were originally invented to overcome the problems of events. But Semaphores were only somewhat better, and so condition variables and various patterns were developed. Dijkstra's 1965 paper "Cooperating Sequential Processes" explained the problems associated with events and any other constructs that depend on processing speed assumptions to work correctly. That paper was recently reprinted in the book "The Origin of Concurrent Programming", Per Brinch Hansen, editor, 2002. Dijkstra died last year. Brinch Hansen and Hoare, the other giants of concurrent programming, are still active in computer science. Hoare has been knighted, the only programmer that I know of to gain that honor. Brinch Hansen has written some interesting recent papers pointing out problems with Java multi-tasking semantics. --Beman

Beman Dawes wrote: [...]
Hoare
http://research.microsoft.com/~thoare
has been knighted, the only programmer that I know of to gain that honor.
Well, just a few months ago MS Software Architect (typical programming job, oder? ;-) ) Mr. Gates has been awarded a Knighthood (KBE of "a Knight Commander of the Most Excellent Order of the British Empire") by Her Majesty the Queen. MS is full of knights, you know. ;-) ;-) regards, alexander.

For what it's worth, the version of Boost.Thread in the thread_dev branch of CVS already uses a critical section when possible. As time allows, I'm planning to review the code in the thread_dev branch, ask for comments, finish anything that isn't finished, and move it to the main branch piece by piece. I'm currently doing this (very slowly, I'm afraid) for the thread_specific_ptr and related classes. Mike Adal Chiriliuc wrote:
Hello.
try_mutex uses a Mutex instead of a CRITICAL_SECTION because on Windows 9x and Me it's impossible to try the aquisition of a CRITICAL_SECTION.
On Windows NT/2000/XP this is possible, using the function TryEnterCriticalSection.
So I suggest this: choose at runtime what syncronization primitive to use based on the running OS. CRITICAL_SECTIONS are much faster and since Windows NT is gradually replacing Windows 9x systems I think it is good to use this feature.
A sketch follows.
---------- mutex.cpp -----------
namespace { bool g_Init = false; bool g_UseTryCritical; TryCriticalProcType g_TryCritical;
void TryCriticalInit() { g_Init = true; g_UseTryCritical = false; get windows version; if (!windows nt) return; lib = load library (kernel32.dll); g_TryCritical = get proc address (lib, TryEnterCriticalSection); g_UseTryCritical = true; }
}
try_mutex::try_mutex() { if (!g_Init) TryCriticalInit(); if (g_UseTryCritical) { try { m_mutex = reinterpret_cast<void*>(new CRITICAL_SECTION); } catch (...) { } } else { m_mutex = reinterpret_cast<void*>(CreateMutex(0, 0, 0)); } if (!m_mutex) throw thread_resource_error(); if (g_UseTryCritical)
InitializeCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex ));
}
bool try_mutex::do_trylock() { if (g_UseTryCritical) { return g_TryCritical(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex)) != 0; } else { unsigned int res = 0; res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), 0); assert(res != WAIT_FAILED && res != WAIT_ABANDONED); return res == WAIT_OBJECT_0; } }
---------- mutex.cpp -----------
There is only one potential problem: if (!g_Init) TryCriticalInit(); might not be thread safe.
Something like a thread safe singleton might be needed. Or maybe we could use InterlockedExchange & co.
I'm not sure if timed_mutex could also benefit from this.
In the docs you should explain what joining means. I worked a lot with threads in the past, but I've never heard of such a concept. I had to look through the source codes to understand what join means. Many Win32 developers will not know of concepts not covered by the Platform SDK. You do explain that conditions are some sort of events, but you use joining in sample code without explaining what it does.
Regards, Adal Chiriliuc
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Monday, March 22, 2004 Michael Glassford wrote:
For what it's worth, the version of Boost.Thread in the thread_dev branch of CVS already uses a critical section when possible. As time allows, I'm planning to review the code in the thread_dev branch, ask for comments, finish anything that isn't finished, and move it to the main branch piece by piece. I'm currently doing this (very slowly, I'm afraid) for the thread_specific_ptr and related classes.
This file? Sorry, but I have little CVS experience. http://cvs.sourceforge.net/viewcvs.py/*checkout*/boost/boost/libs/thread/src... http://cvs.sourceforge.net/viewcvs.py/*checkout*/boost/boost/libs/thread/src/mutex.cpp?content-type=text%2Fplain&rev=1.11.4.4 It does not do what I have suggested. try_mutex still uses a real mutex instead of a CRITICAL_SECTION on Windows NT. I did not suggest to use a CRITICAL_SECTION when not supporting the trylock operation, this is already done. I suggested to check at runtime the operating system version and choose the best option. Adal Chiriliuc

On Monday, March 22, 2004 Michael Glassford wrote:
For what it's worth, the version of Boost.Thread in the thread_dev branch of CVS already uses a critical section when possible. As time allows, I'm planning to review the code in the thread_dev branch, ask for comments, finish anything that isn't finished, and move it to
Sorry, you're right; I read too quickly. The mutex class was changed so that it uses a mutex if necessary (i.e., if it must be named--a feature that isn't in the current Boost.Thread) and a critical section otherwise. You're right that the try_mutex still uses a mutex, not a critical section, and a solution like what you describe is probably a good idea. However, why not call your TryCriticalInit() function using the Boost.Thread call_once() function? I'll work on something like this and look at timed_mutex when I move the mutex implementation from the thread_dev to the main branch. Mike Adal Chiriliuc wrote: the
main branch piece by piece. I'm currently doing this (very slowly, I'm afraid) for the thread_specific_ptr and related classes.
This file? Sorry, but I have little CVS experience.
http://cvs.sourceforge.net/viewcvs.py/*checkout*/boost/boost/libs/thread/src...
It does not do what I have suggested. try_mutex still uses a real mutex instead of a CRITICAL_SECTION on Windows NT.
I did not suggest to use a CRITICAL_SECTION when not supporting the trylock operation, this is already done. I suggested to check at runtime the operating system version and choose the best option.
Adal Chiriliuc
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Monday, March 22, 2004 Michael Glassford wrote:
However, why not call your TryCriticalInit() function using the Boost.Thread call_once() function?
I've thought about using call_once, but I worried that it might be possible for this function to use internally a boost mutex so we would have a circular dependency. I looked now through it's code and it seems safe to use.
I'll work on something like this and look at timed_mutex when I move the mutex implementation from the thread_dev to the main branch.
OK. Adal Chiriliuc
participants (7)
-
Adal Chiriliuc
-
Alexander Terekhov
-
Beman Dawes
-
Carl Daniel
-
David Abrahams
-
John Torjo
-
Michael Glassford