|
| 1 | +### 安卓当中的同步机制 |
| 2 | + |
| 3 | +我们先回忆一下,操作系统中有哪些同步的手段? |
| 4 | + |
| 5 | +- 信号量(Semaphore) |
| 6 | + |
| 7 | + 它主要包含两个操作,P和V。 Semaphore 来指示这个共享资源的可用数量,换种理解是,有多少人可以使用这个资源? |
| 8 | + |
| 9 | + P操作可以减少信号量的计数,V操作可以增加计数。P V 这两个操作都是属于原子操作,意味着执行过程不可以被中断。 |
| 10 | + |
| 11 | +- 互斥量(Mutex) |
| 12 | + |
| 13 | + 简单而言,你可以认为他是简化版的信号量。也就是取值只能为0和1的信号量(Binary Semaphore)。换句话说,操作系统很多时候的资源都具有排他性--这个资源当前要么被占用,要么可以被访问。Mutex相较于Semaphore实现起来也更为简单。 |
| 14 | + |
| 15 | +- 管程(Monitor) |
| 16 | + |
| 17 | +- Futex(Fast Userspace mutexs)Linux独有的,好像在早期的某个内核版本被加入。 |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | +那么我们来看看Android中有哪些手段吧。 |
| 22 | + |
| 23 | +#### Mutex |
| 24 | + |
| 25 | +文件位置:`$ANDROID_CODEBASE/system/core/libutils/include/utils/Mutex.h`,如果下面没有特殊说明,默认我们的路径都是从`$ANDROID_CODEBASE`开始。它表示你的源码根目录。 |
| 26 | + |
| 27 | +为了简单,我就摘取一些相关的代码,其他代码有兴趣的可以自行深入分析。 |
| 28 | + |
| 29 | +```c++ |
| 30 | +class CAPABILITY("mutex") Mutex { |
| 31 | + public: |
| 32 | + enum { |
| 33 | + PRIVATE = 0, // 只支持同一个进程间的同步 |
| 34 | + SHARED = 1 // 支持跨进程间的同步 |
| 35 | + }; |
| 36 | + |
| 37 | + Mutex(); |
| 38 | + explicit Mutex(const char* name); |
| 39 | + explicit Mutex(int type, const char* name = nullptr); |
| 40 | + ~Mutex(); |
| 41 | + |
| 42 | + // lock or unlock the mutex |
| 43 | + status_t lock() ACQUIRE(); |
| 44 | + void unlock() RELEASE(); |
| 45 | + |
| 46 | + // lock if possible; returns 0 on success, error otherwise |
| 47 | + status_t tryLock() TRY_ACQUIRE(0); |
| 48 | + ... |
| 49 | +``` |
| 50 | +
|
| 51 | +
|
| 52 | +
|
| 53 | +两个枚举量的意思我已经注释中写明白了,这里只有三个有意思的方法, |
| 54 | +
|
| 55 | +```c++ |
| 56 | +status_t lock(); |
| 57 | +void unlock(); |
| 58 | +status_t tryLock(); |
| 59 | +``` |
| 60 | + |
| 61 | +从名字上我们可以看到他们的作用,我们来进一步看看他的实现: |
| 62 | + |
| 63 | +```c++ |
| 64 | +inline status_t Mutex::lock() { |
| 65 | + return -pthread_mutex_lock(&mMutex); |
| 66 | +} |
| 67 | +inline void Mutex::unlock() { |
| 68 | + pthread_mutex_unlock(&mMutex); |
| 69 | +} |
| 70 | +inline status_t Mutex::tryLock() { |
| 71 | + return -pthread_mutex_trylock(&mMutex); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +这里你就会发现,他本质上其实是对pthread的接口做了一些相关的封装 。 |
| 76 | + |
| 77 | +#### Condition |
| 78 | + |
| 79 | +这个东西字面意思是条件。他的设计哲学是,判断一个条件是不是满足了?—— 如果满足了,那就返回,继续执行下去,否则就休眠等待。**直到条件被满足**。 |
| 80 | + |
| 81 | +退一步想想,这种情况能用Mutex做么? 理论上应该可以的。举个例子,假设我们两个线程`A` ,` B`, 他们会同时修改一个全局变量`var`, 并且我们定义行为: |
| 82 | + |
| 83 | +`Thread A` 不断修改 `var`的值,每次改变之后的值是未知的 |
| 84 | + |
| 85 | +`Thread B`观察`var`的值,如果为0了的时候执行某些动作。 |
| 86 | + |
| 87 | +我们可以看到, A , B 两个线程都想访问`var`这个资源。Mutex是个思路。但是我们想想,线程B 等待的是当 `var`这个变量为0的情况,醉翁之意不在酒。 |
| 88 | + |
| 89 | +如果用Mutex写,类似于这样的: |
| 90 | + |
| 91 | +```c |
| 92 | +while (1) { // 死循环 |
| 93 | + acquire_mutex_lock(var); // 去获取mutex锁 |
| 94 | + if (var == 0) { // 条件满足 |
| 95 | + release_mutex_lock(var); |
| 96 | + break; |
| 97 | + }else { |
| 98 | + release_mutex_lock(var); // 下一轮我们再看看 |
| 99 | + sleep(); |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +所以我们可以看到,这种轮询方式特别耗费CPU时间。 |
| 105 | + |
| 106 | +举个例子,假如有两个角色,厕所维护员还有使用厕所的用户,代表上述A B 两个角色。然后我们把厕纸当做变量var。那么出现这种场景,用户随便用厕纸,厕纸的余量是多少未知。但是工作人员等厕纸余量为0的时候,需要去更换厕纸。Mutex的机制下,可以看到,工作人员和普通拉屎的用户一样,都要排队轮询进厕所来看看厕纸。那么可以看到,这个工作人员效率很低,他要跟大家一起排队来获取进入厕所的机会。 |
| 107 | + |
| 108 | +那么有一种思路是,工作人员不排队,当厕纸用完了,有个人通知他,叫他进来换厕纸,减少排队数量,提高效率。 |
| 109 | + |
| 110 | +Condition就是来解决这类问题的。 |
| 111 | + |
| 112 | +- 文件位置:`system/core/libutils/include/utils/Condition.h` |
| 113 | + |
| 114 | +```c++ |
| 115 | +class Condition { |
| 116 | +public: |
| 117 | + enum { |
| 118 | + PRIVATE = 0, // 和前面类似,也有跨进程共享的支持 |
| 119 | + SHARED = 1 |
| 120 | + }; |
| 121 | + |
| 122 | + enum WakeUpType { |
| 123 | + WAKE_UP_ONE = 0, |
| 124 | + WAKE_UP_ALL = 1 |
| 125 | + }; |
| 126 | + |
| 127 | + Condition(); |
| 128 | + explicit Condition(int type); |
| 129 | + ~Condition(); |
| 130 | + // Wait on the condition variable. Lock the mutex before calling. |
| 131 | + // Note that spurious wake-ups may happen. |
| 132 | + status_t wait(Mutex& mutex); // 在某个条件上等待 |
| 133 | + // same with relative timeout |
| 134 | + status_t waitRelative(Mutex& mutex, nsecs_t reltime); // 同上,但是这里是超时退出 |
| 135 | + // Signal the condition variable, allowing one thread to continue. |
| 136 | + void signal(); // 满足条件了,通知等待者 |
| 137 | + // Signal the condition variable, allowing one or all threads to continue. |
| 138 | + void signal(WakeUpType type) { |
| 139 | + if (type == WAKE_UP_ONE) { |
| 140 | + signal(); |
| 141 | + } else { |
| 142 | + broadcast(); |
| 143 | + } |
| 144 | + } |
| 145 | + // Signal the condition variable, allowing all threads to continue. |
| 146 | + void broadcast(); // 条件满足时 通知所有等待者 |
| 147 | +``` |
| 148 | + |
| 149 | +其实如果你英文好的话,看上面的注释你就可以明白了。 |
| 150 | + |
| 151 | +这里是个C++的类,那么我们来看看这里的一些关键实现方法。 |
| 152 | + |
| 153 | +```c++ |
| 154 | +inline status_t Condition::wait(Mutex& mutex) { |
| 155 | + return -pthread_cond_wait(&mCond, &mutex.mMutex); |
| 156 | +} |
| 157 | + |
| 158 | +inline status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) { |
| 159 | + struct timespec ts; |
| 160 | +#if defined(__linux__) |
| 161 | + clock_gettime(CLOCK_MONOTONIC, &ts); // linux和apple下获取时间的函数,编译相关 |
| 162 | +#else // __APPLE__ |
| 163 | + // Apple doesn't support POSIX clocks. |
| 164 | + struct timeval t; |
| 165 | + gettimeofday(&t, nullptr); |
| 166 | + ts.tv_sec = t.tv_sec; |
| 167 | + ts.tv_nsec = t.tv_usec*1000; |
| 168 | +#endif |
| 169 | + |
| 170 | + // On 32-bit devices, tv_sec is 32-bit, but `reltime` is 64-bit. |
| 171 | + int64_t reltime_sec = reltime/1000000000; |
| 172 | + |
| 173 | + ts.tv_nsec += static_cast<long>(reltime%1000000000); |
| 174 | + if (reltime_sec < INT64_MAX && ts.tv_nsec >= 1000000000) { |
| 175 | + ts.tv_nsec -= 1000000000; |
| 176 | + ++reltime_sec; |
| 177 | + } |
| 178 | + |
| 179 | + int64_t time_sec = ts.tv_sec; |
| 180 | + if (time_sec > INT64_MAX - reltime_sec) { |
| 181 | + time_sec = INT64_MAX; |
| 182 | + } else { |
| 183 | + time_sec += reltime_sec; |
| 184 | + } |
| 185 | + |
| 186 | + ts.tv_sec = (time_sec > LONG_MAX) ? LONG_MAX : static_cast<long>(time_sec); |
| 187 | + |
| 188 | + return -pthread_cond_timedwait(&mCond, &mutex.mMutex, &ts); |
| 189 | +} |
| 190 | + |
| 191 | + |
| 192 | +inline void Condition::signal() { |
| 193 | + pthread_cond_signal(&mCond); |
| 194 | +} |
| 195 | +inline void Condition::broadcast() { |
| 196 | + pthread_cond_broadcast(&mCond); |
| 197 | +} |
| 198 | + |
| 199 | +``` |
| 200 | +
|
| 201 | +可以看到,还是pthread的相关接口的封装。 |
| 202 | +
|
| 203 | +这里留个问题,为什么wait函数的参数还是要用到Mutex呢? |
| 204 | +
|
| 205 | +#### Barrier |
| 206 | +
|
| 207 | +意思是屏障。接上文我们继续思考,我们来看一个Condition的例子。 |
| 208 | +
|
| 209 | +Barrier这个东西是为了SurfaceFlinger这个东西设计的,不像Mutex,Condition一样作为Util工具来提供给大家用。不过我们来看看Barrier这个例子。 |
| 210 | +
|
| 211 | +文件位置:`frameworks/native/services/surfaceflinger/Barrier.h` |
| 212 | +
|
| 213 | +```c++ |
| 214 | +class Barrier |
| 215 | +{ |
| 216 | +public: |
| 217 | + // Release any threads waiting at the Barrier. |
| 218 | + // Provides release semantics: preceding loads and stores will be visible |
| 219 | + // to other threads before they wake up. |
| 220 | + void open() { |
| 221 | + std::lock_guard<std::mutex> lock(mMutex); |
| 222 | + mIsOpen = true; |
| 223 | + mCondition.notify_all(); |
| 224 | + } |
| 225 | +
|
| 226 | + // Reset the Barrier, so wait() will block until open() has been called. |
| 227 | + void close() { |
| 228 | + std::lock_guard<std::mutex> lock(mMutex); |
| 229 | + mIsOpen = false; |
| 230 | + } |
| 231 | +
|
| 232 | + // Wait until the Barrier is OPEN. |
| 233 | + // Provides acquire semantics: no subsequent loads or stores will occur |
| 234 | + // until wait() returns. |
| 235 | + void wait() const { |
| 236 | + std::unique_lock<std::mutex> lock(mMutex); |
| 237 | + mCondition.wait(lock, [this]() NO_THREAD_SAFETY_ANALYSIS { return mIsOpen; }); |
| 238 | + } |
| 239 | +private: |
| 240 | + mutable std::mutex mMutex; |
| 241 | + mutable std::condition_variable mCondition; |
| 242 | + int mIsOpen GUARDED_BY(mMutex){false}; |
| 243 | +}; |
| 244 | +
|
| 245 | +``` |
| 246 | + |
| 247 | +可以看到,有三个函数,分别是`wait`, `close`,`open`。 |
| 248 | + |
| 249 | +既然说它是condition的一个例子,那么barrier等待的条件是什么呢? |
| 250 | + |
| 251 | +答案是 `mIsOpen`。我们来看`wait`函数,他首先获取了一个mutex锁,然后才去调用Condition的`wait`。为什么呢?因为`mIsOpen`这个变量如果没有被互斥锁保护起来的话,`open/close`同时去操作他的话,会发生什么情况呢? |
| 252 | + |
| 253 | +所以这就是整个设计的一个巧妙的地方。 |
0 commit comments