diff options
author | arthurhsu@google.com <arthurhsu@google.com@672e30a5-4c29-85ac-ac6d-611c735e0a51> | 2011-09-12 20:07:59 +0000 |
---|---|---|
committer | arthurhsu@google.com <arthurhsu@google.com@672e30a5-4c29-85ac-ac6d-611c735e0a51> | 2011-09-12 20:07:59 +0000 |
commit | 053885bde4e83c53465ea42257ba056d083afb07 (patch) | |
tree | de40261275db23cc155a7011a1d0b0590559f6e7 | |
parent | 6b8e073e978eed96605da6f92d6db740a39864ba (diff) | |
download | src-053885bde4e83c53465ea42257ba056d083afb07.tar.gz |
Add mutex
git-svn-id: http://sfntly.googlecode.com/svn/trunk/cpp/src@72 672e30a5-4c29-85ac-ac6d-611c735e0a51
-rw-r--r-- | sfntly/port/lock.cc | 72 | ||||
-rw-r--r-- | sfntly/port/lock.h | 76 | ||||
-rw-r--r-- | sfntly/port/type.h | 6 | ||||
-rw-r--r-- | test/lock_test.cc | 244 | ||||
-rw-r--r-- | test/platform_thread.cc | 101 | ||||
-rw-r--r-- | test/platform_thread.h | 75 |
6 files changed, 574 insertions, 0 deletions
diff --git a/sfntly/port/lock.cc b/sfntly/port/lock.cc new file mode 100644 index 0000000..6c0c309 --- /dev/null +++ b/sfntly/port/lock.cc @@ -0,0 +1,72 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sfntly/port/lock.h" + +namespace sfntly { + +#if defined (WIN32) + +Lock::Lock() { + // The second parameter is the spin count, for short-held locks it avoid the + // contending thread from going to sleep which helps performance greatly. + ::InitializeCriticalSectionAndSpinCount(&os_lock_, 2000); +} + +Lock::~Lock() { + ::DeleteCriticalSection(&os_lock_); +} + +bool Lock::Try() { + if (::TryEnterCriticalSection(&os_lock_) != FALSE) { + return true; + } + return false; +} + +void Lock::Acquire() { + ::EnterCriticalSection(&os_lock_); +} + +void Lock::Unlock() { + ::LeaveCriticalSection(&os_lock_); +} + +#else // We assume it's pthread + +Lock::Lock() { + pthread_mutex_init(&os_lock_, NULL); +} + +Lock::~Lock() { + pthread_mutex_destroy(&os_lock_); +} + +bool Lock::Try() { + return (pthread_mutex_trylock(&os_lock_) == 0); +} + +void Lock::Acquire() { + pthread_mutex_lock(&os_lock_); +} + +void Lock::Unlock() { + pthread_mutex_unlock(&os_lock_); +} + +#endif + +} // namespace sfntly diff --git a/sfntly/port/lock.h b/sfntly/port/lock.h new file mode 100644 index 0000000..b2e29bf --- /dev/null +++ b/sfntly/port/lock.h @@ -0,0 +1,76 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SFNTLY_CPP_SRC_SFNTLY_PORT_LOCK_H_ +#define SFNTLY_CPP_SRC_SFNTLY_PORT_LOCK_H_ + +#if defined (WIN32) +#include <windows.h> +#else // Assume pthread. +#include <pthread.h> +#include <errno.h> +#endif + +#include "sfntly/port/type.h" + +namespace sfntly { + +#if defined (WIN32) + typedef CRITICAL_SECTION OSLockType; +#else // Assume pthread. + typedef pthread_mutex_t OSLockType; +#endif + +class Lock { + public: + Lock(); + ~Lock(); + + // If the lock is not held, take it and return true. If the lock is already + // held by something else, immediately return false. + bool Try(); + + // Take the lock, blocking until it is available if necessary. + void Acquire(); + + // Release the lock. This must only be called by the lock's holder: after + // a successful call to Try, or a call to Lock. + void Unlock(); + + private: + OSLockType os_lock_; + NO_COPY_AND_ASSIGN(Lock); +}; + +// A helper class that acquires the given Lock while the AutoLock is in scope. +class AutoLock { + public: + explicit AutoLock(Lock& lock) : lock_(lock) { + lock_.Acquire(); + } + + ~AutoLock() { + lock_.Unlock(); + } + + private: + Lock& lock_; + NO_COPY_AND_ASSIGN(AutoLock); +}; + +} // namespace sfntly + +#endif // SFNTLY_CPP_SRC_SFNTLY_PORT_LOCK_H_ diff --git a/sfntly/port/type.h b/sfntly/port/type.h index 458a364..20a5ba8 100644 --- a/sfntly/port/type.h +++ b/sfntly/port/type.h @@ -56,6 +56,12 @@ typedef std::vector<byte_t> ByteVector; typedef std::vector<int32_t> IntegerList; typedef std::set<int32_t> IntegerSet; +// A macro to disallow the copy constructor and operator= functions. +// This should be used in the private: declarations for a class. +#define NO_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + } // namespace sfntly // Make google3 happy since it prohibits RTTI. diff --git a/test/lock_test.cc b/test/lock_test.cc new file mode 100644 index 0000000..b29a4bf --- /dev/null +++ b/test/lock_test.cc @@ -0,0 +1,244 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include "gtest/gtest.h" +#include "sfntly/port/lock.h" +#include "test/platform_thread.h" + +namespace sfntly { + +// Basic test to make sure that Acquire()/Unlock()/Try() don't crash + +class BasicLockTestThread : public PlatformThread::Delegate { + public: + BasicLockTestThread(Lock* lock) : lock_(lock), acquired_(0) {} + + virtual void ThreadMain() { + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + lock_->Unlock(); + } + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + PlatformThread::Sleep(rand() % 20); + lock_->Unlock(); + } + for (int i = 0; i < 10; i++) { + if (lock_->Try()) { + acquired_++; + PlatformThread::Sleep(rand() % 20); + lock_->Unlock(); + } + } + } + + int acquired() const { return acquired_; } + + private: + Lock* lock_; + int acquired_; + + NO_COPY_AND_ASSIGN(BasicLockTestThread); +}; + +bool BasicLockTest() { + Lock lock; + BasicLockTestThread thread(&lock); + PlatformThreadHandle handle = kNullThreadHandle; + + EXPECT_TRUE(PlatformThread::Create(&thread, &handle)); + + int acquired = 0; + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + lock.Unlock(); + } + for (int i = 0; i < 10; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(rand() % 20); + lock.Unlock(); + } + for (int i = 0; i < 10; i++) { + if (lock.Try()) { + acquired++; + PlatformThread::Sleep(rand() % 20); + lock.Unlock(); + } + } + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(rand() % 20); + lock.Unlock(); + } + + PlatformThread::Join(handle); + + EXPECT_GE(acquired, 20); + EXPECT_GE(thread.acquired(), 20); + + return true; +} + +// Test that Try() works as expected ------------------------------------------- + +class TryLockTestThread : public PlatformThread::Delegate { + public: + TryLockTestThread(Lock* lock) : lock_(lock), got_lock_(false) {} + + virtual void ThreadMain() { + got_lock_ = lock_->Try(); + if (got_lock_) + lock_->Unlock(); + } + + bool got_lock() const { return got_lock_; } + + private: + Lock* lock_; + bool got_lock_; + + NO_COPY_AND_ASSIGN(TryLockTestThread); +}; + +bool TryLockTest() { + Lock lock; + + EXPECT_TRUE(lock.Try()); + // We now have the lock.... + + // This thread will not be able to get the lock. + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle = kNullThreadHandle; + + EXPECT_TRUE(PlatformThread::Create(&thread, &handle)); + + PlatformThread::Join(handle); + + EXPECT_FALSE(thread.got_lock()); + } + + lock.Unlock(); + + // This thread will.... + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle = kNullThreadHandle; + + EXPECT_TRUE(PlatformThread::Create(&thread, &handle)); + + PlatformThread::Join(handle); + + EXPECT_TRUE(thread.got_lock()); + // But it released it.... + EXPECT_TRUE(lock.Try()); + } + + lock.Unlock(); + return true; +} + +// Tests that locks actually exclude ------------------------------------------- + +class MutexLockTestThread : public PlatformThread::Delegate { + public: + MutexLockTestThread(Lock* lock, int* value) : lock_(lock), value_(value) {} + + // Static helper which can also be called from the main thread. + static void DoStuff(Lock* lock, int* value) { + for (int i = 0; i < 40; i++) { + lock->Acquire(); + int v = *value; + PlatformThread::Sleep(rand() % 10); + *value = v + 1; + lock->Unlock(); + } + } + + virtual void ThreadMain() { + DoStuff(lock_, value_); + } + + private: + Lock* lock_; + int* value_; + + NO_COPY_AND_ASSIGN(MutexLockTestThread); +}; + +bool MutexTwoThreads() { + Lock lock; + int value = 0; + + MutexLockTestThread thread(&lock, &value); + PlatformThreadHandle handle = kNullThreadHandle; + + EXPECT_TRUE(PlatformThread::Create(&thread, &handle)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle); + + EXPECT_EQ(2 * 40, value); + return true; +} + +bool MutexFourThreads() { + Lock lock; + int value = 0; + + MutexLockTestThread thread1(&lock, &value); + MutexLockTestThread thread2(&lock, &value); + MutexLockTestThread thread3(&lock, &value); + PlatformThreadHandle handle1 = kNullThreadHandle; + PlatformThreadHandle handle2 = kNullThreadHandle; + PlatformThreadHandle handle3 = kNullThreadHandle; + + EXPECT_TRUE(PlatformThread::Create(&thread1, &handle1)); + EXPECT_TRUE(PlatformThread::Create(&thread2, &handle2)); + EXPECT_TRUE(PlatformThread::Create(&thread3, &handle3)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle1); + PlatformThread::Join(handle2); + PlatformThread::Join(handle3); + + EXPECT_EQ(4 * 40, value); + return true; +} + +} // namespace sfntly + +TEST(LockTest, Basic) { + ASSERT_TRUE(sfntly::BasicLockTest()); +} + +TEST(LockTest, TryLock) { + ASSERT_TRUE(sfntly::TryLockTest()); +} + +TEST(LockTest, Mutex) { + ASSERT_TRUE(sfntly::MutexTwoThreads()); + ASSERT_TRUE(sfntly::MutexFourThreads()); +} diff --git a/test/platform_thread.cc b/test/platform_thread.cc new file mode 100644 index 0000000..6a0b84b --- /dev/null +++ b/test/platform_thread.cc @@ -0,0 +1,101 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/platform_thread.h" + +namespace sfntly { + +#if defined (WIN32) + +DWORD __stdcall ThreadFunc(void* params) { + PlatformThread::Delegate* delegate = + static_cast<PlatformThread::Delegate*>(params); + delegate->ThreadMain(); + return 0; +} + +// static +bool PlatformThread::Create(Delegate* delegate, + PlatformThreadHandle* thread_handle) { + assert(thread_handle); + *thread_handle = CreateThread(NULL, 0, ThreadFunc, delegate, 0, NULL); + if (!(*thread_handle)) { + return false; + } + + return true; +} + +// static +void PlatformThread::Join(PlatformThreadHandle thread_handle) { + assert(thread_handle); + DWORD result = WaitForSingleObject(thread_handle, INFINITE); + assert(result == WAIT_OBJECT_0); + CloseHandle(thread_handle); +} + +// static +void PlatformThread::Sleep(int32_t duration_ms) { + ::Sleep(duration_ms); +} + +#else + +void* ThreadFunc(void* params) { + PlatformThread::Delegate* delegate = + static_cast<PlatformThread::Delegate*>(params); + delegate->ThreadMain(); + return NULL; +} + +// static +bool PlatformThread::Create(Delegate* delegate, + PlatformThreadHandle* thread_handle) { + assert(thread_handle); + + bool success = false; + pthread_attr_t attributes; + pthread_attr_init(&attributes); + success = !pthread_create(thread_handle, &attributes, ThreadFunc, delegate); + pthread_attr_destroy(&attributes); + + return success; +} + +// static +void PlatformThread::Join(PlatformThreadHandle thread_handle) { + assert(thread_handle); + pthread_join(thread_handle, NULL); +} + +// static +void PlatformThread::Sleep(int32_t duration_ms) { + struct timespec sleep_time, remaining; + + // Contains the portion of duration_ms >= 1 sec. + sleep_time.tv_sec = duration_ms / 1000; + duration_ms -= sleep_time.tv_sec * 1000; + + // Contains the portion of duration_ms < 1 sec. + sleep_time.tv_nsec = duration_ms * 1000 * 1000; // nanoseconds. + + while (nanosleep(&sleep_time, &remaining) == -1 && errno == EINTR) + sleep_time = remaining; +} + +#endif // WIN32 + +} // namespace sfntly diff --git a/test/platform_thread.h b/test/platform_thread.h new file mode 100644 index 0000000..f236f4c --- /dev/null +++ b/test/platform_thread.h @@ -0,0 +1,75 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Simple platform thread implementation used to test our cross-platform locks. +// This is a trimmed down version of Chromium base/threading/platform_thread.h. + +#ifndef SFNTLY_CPP_SRC_TEST_PLATFORM_THREAD_H_ +#define SFNTLY_CPP_SRC_TEST_PLATFORM_THREAD_H_ + +#if defined (WIN32) +#include <windows.h> +#else // Assume pthread +#include <errno.h> +#include <pthread.h> +#include <time.h> +#endif // if defined (WIN32) + +#include "sfntly/port/type.h" + +namespace sfntly { + +#if defined (WIN32) +typedef HANDLE PlatformThreadHandle; +const PlatformThreadHandle kNullThreadHandle = NULL; +#else // Assume pthread +typedef pthread_t PlatformThreadHandle; +const PlatformThreadHandle kNullThreadHandle = 0; +#endif + +class PlatformThread { + public: + class Delegate { + public: + virtual ~Delegate() {} + virtual void ThreadMain() = 0; + }; + + // Sleeps for the specified duration (units are milliseconds). + static void Sleep(int32_t duration_ms); + + // Creates a new thread using default stack size. Upon success, + // |*thread_handle| will be assigned a handle to the newly created thread, + // and |delegate|'s ThreadMain method will be executed on the newly created + // thread. + // NOTE: When you are done with the thread handle, you must call Join to + // release system resources associated with the thread. You must ensure that + // the Delegate object outlives the thread. + static bool Create(Delegate* delegate, PlatformThreadHandle* thread_handle); + + // Joins with a thread created via the Create function. This function blocks + // the caller until the designated thread exits. This will invalidate + // |thread_handle|. + static void Join(PlatformThreadHandle thread_handle); + +private: + PlatformThread() {} + NO_COPY_AND_ASSIGN(PlatformThread); +}; + +} // namespace sfntly + +#endif // SFNTLY_CPP_SRC_TEST_PLATFORM_THREAD_H_ |