summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorarthurhsu@google.com <arthurhsu@google.com@672e30a5-4c29-85ac-ac6d-611c735e0a51>2011-09-12 20:07:59 +0000
committerarthurhsu@google.com <arthurhsu@google.com@672e30a5-4c29-85ac-ac6d-611c735e0a51>2011-09-12 20:07:59 +0000
commit053885bde4e83c53465ea42257ba056d083afb07 (patch)
treede40261275db23cc155a7011a1d0b0590559f6e7
parent6b8e073e978eed96605da6f92d6db740a39864ba (diff)
downloadsrc-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.cc72
-rw-r--r--sfntly/port/lock.h76
-rw-r--r--sfntly/port/type.h6
-rw-r--r--test/lock_test.cc244
-rw-r--r--test/platform_thread.cc101
-rw-r--r--test/platform_thread.h75
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_