1. 什么是忙碌等待?
在软件工程中,忙碌等待(也称自旋;英语:Busy waiting、busy-looping、spinning)是一种以进程反复检查一个条件是否为真为根本的技术,条件可能为键盘输入或某个锁是否可用。忙碌等待也可以用来产生一个任意的时间延迟,若系统没有提供生成特定时间长度的方法,则需要用到忙碌等待。不同的计算机处理器速度差异很大,特别是一些处理器设计为可能根据外部因素(例如操作系统上的负载)动态调整速率。因此,忙碌等待这种时间延迟技术容易产生不可预知、甚至不一致的结果,除非实现代码来确定处理器执行“什么都不做”循环的速度,或者循环代码明确检查实时时钟。
在某些情况下,忙碌等待是有效的策略,特别是实现自旋锁设计的操作系统上运行对称多处理。不过一般来说,忙碌等待是应该避免的反模式[1],处理器时间应该用来执行其他任务,而不是浪费在无用的活动上。
对于多核CPU,忙碌等待的优点是不切换线程,避免了由此付出的代价。因此一些多线程同步机制不使用切换到内核态的同步对象,而是以用户态的自旋锁或其衍生机制(如轻型读写锁)来做同步,付出的时间复杂度相差3个数量级。忙碌等待可使用一些机制来降低CPU功耗,如Windows系统中调用YieldProcessor,实际上是调用了SIMD指令_mm_pause。
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> volatile int i = 0; /* i is global, so it is visible to all functions. It's also marked volatile, because it may change in a way which is not predictable by the compiler, here from a different thread. */ /* f1 uses a spinlock to wait for i to change from 0. */ static void *f1(void *p) { while (i == 0) { /* do nothing - just keep checking over and over */ } printf("i's value has changed to %d.\n", i); return NULL; } static void *f2(void *p) { sleep(60); /* sleep for 60 seconds */ i = 99; printf("t2 has changed the value of i to %d.\n", i); return NULL; } int main() { int rc; pthread_t t1, t2; rc = pthread_create(&t1, NULL, f1, NULL); if (rc != 0) { fprintf(stderr, "pthread f1 failed\n"); return EXIT_FAILURE; } rc = pthread_create(&t2, NULL, f2, NULL); if (rc != 0) { fprintf(stderr, "pthread f2 failed\n"); return EXIT_FAILURE; } pthread_join(t1, NULL); pthread_join(t2, NULL); puts("All pthreads finished."); return 0; }
2. 什么是信号量?为什么需要信号量?
信号标(英语:Semaphore)又称为号志、旗语,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态.
semaphore对象适用于控制一个仅支持有限个用户的共享资源,是一种不需要使用忙碌等待(busy waiting)的方法。
typedef struct { int val; pthread_mutex_t mutex; pthread_cond_t cond; } cam_semaphore_t; static inline void cam_sem_init(cam_semaphore_t *s, int n) { pthread_mutex_init(&(s->mutex), NULL); pthread_cond_init(&(s->cond), NULL); s->val = n; } static inline void cam_sem_post(cam_semaphore_t *s) { pthread_mutex_lock(&(s->mutex)); s->val++; pthread_cond_signal(&(s->cond)); pthread_mutex_unlock(&(s->mutex)); } static inline int cam_sem_wait(cam_semaphore_t *s) { int rc = 0; pthread_mutex_lock(&(s->mutex)); while (s->val == 0) rc = pthread_cond_wait(&(s->cond), &(s->mutex)); s->val--; pthread_mutex_unlock(&(s->mutex)); return rc; } static inline void cam_sem_destroy(cam_semaphore_t *s) { pthread_mutex_destroy(&(s->mutex)); pthread_cond_destroy(&(s->cond)); s->val = 0; }
对着代码,一句一句的细细品味。
当 计数值 为 0 时,相当于一个互斥锁。
3. Android Mutex、Condition
/* * Copyright (C) 2007 The Android Open Source Project * * 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 _LIBS_UTILS_MUTEX_H #define _LIBS_UTILS_MUTEX_H #include <stdint.h> #include <sys/types.h> #include <time.h> #if !defined(_WIN32) # include <pthread.h> #endif #include <utils/Errors.h> #include <utils/Timers.h> // --------------------------------------------------------------------------- namespace android { // --------------------------------------------------------------------------- class Condition; /* * NOTE: This class is for code that builds on Win32. Its usage is * deprecated for code which doesn't build for Win32. New code which * doesn't build for Win32 should use std::mutex and std::lock_guard instead. * * Simple mutex class. The implementation is system-dependent. * * The mutex must be unlocked by the thread that locked it. They are not * recursive, i.e. the same thread can't lock it multiple times. */ class Mutex { public: enum { PRIVATE = 0, SHARED = 1 }; Mutex(); explicit Mutex(const char* name); explicit Mutex(int type, const char* name = NULL); ~Mutex(); // lock or unlock the mutex status_t lock(); void unlock(); // lock if possible; returns 0 on success, error otherwise status_t tryLock(); #if defined(__ANDROID__) // Lock the mutex, but don't wait longer than timeoutNs (relative time). // Returns 0 on success, TIMED_OUT for failure due to timeout expiration. // // OSX doesn't have pthread_mutex_timedlock() or equivalent. To keep // capabilities consistent across host OSes, this method is only available // when building Android binaries. // // FIXME?: pthread_mutex_timedlock is based on CLOCK_REALTIME, // which is subject to NTP adjustments, and includes time during suspend, // so a timeout may occur even though no processes could run. // Not holding a partial wakelock may lead to a system suspend. status_t timedLock(nsecs_t timeoutNs); #endif // Manages the mutex automatically. It'll be locked when Autolock is // constructed and released when Autolock goes out of scope. class Autolock { // 非常常用的 Autolock,其作用范围是一个 大括号 public: inline explicit Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); } inline explicit Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); } inline ~Autolock() { mLock.unlock(); } private: Mutex& mLock; }; private: friend class Condition; // A mutex cannot be copied Mutex(const Mutex&); Mutex& operator = (const Mutex&); #if !defined(_WIN32) pthread_mutex_t mMutex; #else void _init(); void* mState; #endif }; // --------------------------------------------------------------------------- #if !defined(_WIN32) inline Mutex::Mutex() { pthread_mutex_init(&mMutex, NULL); } inline Mutex::Mutex(__attribute__((unused)) const char* name) { pthread_mutex_init(&mMutex, NULL); } inline Mutex::Mutex(int type, __attribute__((unused)) const char* name) { if (type == SHARED) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&mMutex, &attr); pthread_mutexattr_destroy(&attr); } else { pthread_mutex_init(&mMutex, NULL); } } inline Mutex::~Mutex() { pthread_mutex_destroy(&mMutex); } inline status_t Mutex::lock() { return -pthread_mutex_lock(&mMutex); } inline void Mutex::unlock() { pthread_mutex_unlock(&mMutex); } inline status_t Mutex::tryLock() { return -pthread_mutex_trylock(&mMutex); } #if defined(__ANDROID__) inline status_t Mutex::timedLock(nsecs_t timeoutNs) { timeoutNs += systemTime(SYSTEM_TIME_REALTIME); const struct timespec ts = { /* .tv_sec = */ static_cast<time_t>(timeoutNs / 1000000000), /* .tv_nsec = */ static_cast<long>(timeoutNs % 1000000000), }; return -pthread_mutex_timedlock(&mMutex, &ts); } #endif #endif // !defined(_WIN32) // --------------------------------------------------------------------------- /* * Automatic mutex. Declare one of these at the top of a function. * When the function returns, it will go out of scope, and release the * mutex. */ typedef Mutex::Autolock AutoMutex; // --------------------------------------------------------------------------- }; // namespace android // --------------------------------------------------------------------------- #endif // _LIBS_UTILS_MUTEX_H
可以看到,Android对于Mutex的实现,其本质也是封装了 pthread_mutex。
/* * Copyright (C) 2007 The Android Open Source Project * * 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 _LIBS_UTILS_CONDITION_H #define _LIBS_UTILS_CONDITION_H #include <limits.h> #include <stdint.h> #include <sys/types.h> #include <time.h> #if !defined(_WIN32) # include <pthread.h> #endif #include <utils/Errors.h> #include <utils/Mutex.h> #include <utils/Timers.h> // --------------------------------------------------------------------------- namespace android { // --------------------------------------------------------------------------- /* * Condition variable class. The implementation is system-dependent. * * Condition variables are paired up with mutexes. Lock the mutex, * call wait(), then either re-wait() if things aren't quite what you want, * or unlock the mutex and continue. All threads calling wait() must * use the same mutex for a given Condition. * * On Android and Apple platforms, these are implemented as a simple wrapper * around pthread condition variables. Care must be taken to abide by * the pthreads semantics, in particular, a boolean predicate must * be re-evaluated after a wake-up, as spurious wake-ups may happen. */ class Condition { public: enum { PRIVATE = 0, SHARED = 1 }; enum WakeUpType { WAKE_UP_ONE = 0, WAKE_UP_ALL = 1 }; Condition(); explicit Condition(int type); ~Condition(); // Wait on the condition variable. Lock the mutex before calling. // Note that spurious wake-ups may happen. status_t wait(Mutex& mutex); // same with relative timeout status_t waitRelative(Mutex& mutex, nsecs_t reltime); // Signal the condition variable, allowing one thread to continue. void signal(); // Signal the condition variable, allowing one or all threads to continue. void signal(WakeUpType type) { if (type == WAKE_UP_ONE) { signal(); } else { broadcast(); } } // Signal the condition variable, allowing all threads to continue. void broadcast(); private: #if !defined(_WIN32) pthread_cond_t mCond; #else void* mState; #endif }; // --------------------------------------------------------------------------- #if !defined(_WIN32) inline Condition::Condition() : Condition(PRIVATE) { } inline Condition::Condition(int type) { pthread_condattr_t attr; pthread_condattr_init(&attr); #if defined(__linux__) pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); #endif if (type == SHARED) { pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); } pthread_cond_init(&mCond, &attr); pthread_condattr_destroy(&attr); } inline Condition::~Condition() { pthread_cond_destroy(&mCond); } inline status_t Condition::wait(Mutex& mutex) { return -pthread_cond_wait(&mCond, &mutex.mMutex); } inline status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) { struct timespec ts; #if defined(__linux__) clock_gettime(CLOCK_MONOTONIC, &ts); #else // __APPLE__ // Apple doesn't support POSIX clocks. struct timeval t; gettimeofday(&t, NULL); ts.tv_sec = t.tv_sec; ts.tv_nsec = t.tv_usec*1000; #endif // On 32-bit devices, tv_sec is 32-bit, but `reltime` is 64-bit. int64_t reltime_sec = reltime/1000000000; ts.tv_nsec += static_cast<long>(reltime%1000000000); if (reltime_sec < INT64_MAX && ts.tv_nsec >= 1000000000) { ts.tv_nsec -= 1000000000; ++reltime_sec; } int64_t time_sec = ts.tv_sec; if (time_sec > INT64_MAX - reltime_sec) { time_sec = INT64_MAX; } else { time_sec += reltime_sec; } ts.tv_sec = (time_sec > LONG_MAX) ? LONG_MAX : static_cast<long>(time_sec); return -pthread_cond_timedwait(&mCond, &mutex.mMutex, &ts); } inline void Condition::signal() { pthread_cond_signal(&mCond); } inline void Condition::broadcast() { pthread_cond_broadcast(&mCond); } #endif // !defined(_WIN32) // --------------------------------------------------------------------------- }; // namespace android // --------------------------------------------------------------------------- #endif // _LIBS_UTILS_CONDITON_H
同样,Condition也是基于 pthread_cond 实现的。
4. pthread_mutex,pthrad_cond
TODO