android常用同步类


原子操作

对于CPU,最小执行单元为一条指令,单条指令在执行过程中是肯定不会打断的。 但是在两条指令之间是可以被打断的,比如中断。而原子操作就类似于单条指令,在原子操作执行期间,不会被其他外部事情打断。
例如对于++运算符,
++i;
执行这条指令大概包括三个步骤:
1.将i的值从内存拷贝到寄存器;
2.在寄存器执行+1;
3.把寄存器中的值拷贝到i所指向的内存中。
上面三步基本就是三条指令,考虑一个多线程的情况,i被多个线程共享,初值 int i = 1, 线程A、B同时对i的值进行++操作,目的是使i的值变为3。
如果线程B在A执行步骤3之前,读取了i的值,此时i的值依然为1,线程B经过++后i的值变为2。而线程A在执行完++操作后,i的值也为2,不是正确的结果3。造成上述的原因就是因为++不是一个原子操作,多线程时对共享资源的处理就会出现问题。

android中原子操作的函数

原子操作的实现依赖与具体硬件,不同的硬件架构实现不同,android 32位相关函数位于system\core\include\cutils\atomic-arm.h中,包括原子赋值,原子加一,原子加等。需要时可以再细查。
例如原子加一函数,

  int32_t android_atomic_inc(volatile int32_t *addr)

但是原子操作的函数种类是有限的,在多核多线程编程中,对于共享资源的访问不可能都用原子操作来实现,应该非常困难,所以有了锁的产生。
Android提供了互斥锁mutex和条件变量,都是linux相关同步函数的包装,封装了以后用起来真是方便。linux原线程函数为pthread_mutex*和pthread_cond*相关函数。

Mutex锁

/*
 * 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.
 */

上面是Mutex类的注释,它的实现是和系统相关的; 锁住mutex的thread(lock()),在执行完成后,必须去解锁(unlock())。
使用Mutex时,首先调用lock函数,去获取锁,使用完成后一定要记得调用unlock去解锁,否则别的线程就无法再获取锁了。

Autolock锁

 // Manages the mutex automatically. It'll be locked when Autolock is
 // constructed and released when Autolock goes out of scope.

为懒人创建的一个类,是Mutex的内部类,根据c++的构造和析构的技巧,在构造时获取锁,在析构时解锁,一一对应,不会漏掉。

使用方法非常简单:
首先定义一个Mutex lock;
在使用这个Mutex的地方直接定义一个Autolock即可,Autolock autolock(lock);

Condition variable

/*
 * 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.
 */

上面是Condition variable类的注释:实现依赖与系统;一般都是结合Mutex一起使用。

Condition适合这样的场景:线程A执行某资源的初始化,初始化完成后,线程A提示线程B开始工作。在线程A未完成初始化工作之前,线程B是不能开始工作的。
Condition提供的函数如下:

     // Wait on the condition variable.  Lock the mutex before calling.
    status_t wait(Mutex& mutex);

等待的线程B调用wait函数,等待condition variable,在调用前需要获取Mutex锁,在函数wait中会自动释放获取到的Mutex锁,然后等待线程B阻塞,等待condition variable条件的达成,一旦条件达成,则重新获取mutex锁,然后从wait返回。

 // same with relative timeout
    status_t waitRelative(Mutex& mutex, nsecs_t reltime);

等待设置一个超时时间,如果事件到了还未等到,则返回。

    // Signal the condition variable, allowing exactly one thread to continue.
    void signal();

线程A在做完初始化工作后调用signal通知线程B条件达成。

   // Signal the condition variable, allowing all threads to continue.
    void broadcast();

线程A在做完初始化工作后调用broadcast通知所有等待的线程条件达成。

但是在pthread_condi_wait()函数的man page中看到一段话:

When using condition variables there is always a Boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. 
Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. 
Since the return from pthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.

在使用condition variables时必须有一个Boolean变量,每个等待条件达成的线程必须根据这个Boolean变量去判断到底是条件达到了吗?
pthread_cond_wait()和pthread_cond_timedwait()函数会由于一些奇怪的原因返回,其实此时条件并未达到。所以这时候需要通过Boolean变量去判断是否是条件达成,如果不是,则继续wait()。

那么这个pthread_cond_wait()函数的返回,其实就没啥实际意义嘛?反正还得需要Boolean变量进行判断,还不如等待的线程一直while循环判断这个Boolean变量,根据结果跳出。
我认为这也是可以的,但是while循环会占用很多的cpu,效率不好。而pthread_cond_wait()相当于线程睡眠了一样,不占多少cpu。
下面是stackoverflow上别人举的一个例子,关于manpage中Spurious wakeups的说明,看完应该会明白。

There are at least two things 'spurious wakeup' could mean:

    A thread blocked in pthread_cond_wait can return from the call even though no call to signal or broadcast on the condition occurred.
    A thread blocked in pthread_cond_wait returns because of a call to signal or broadcast, however after reacquiring the mutex the underlying predicate is found to no longer be true.

But the latter case can occur even if the condition variable implementation does not allow the former case. Consider a producer consumer queue, and three threads.

    Thread 1 has just dequeued an element and released the mutex, and the queue is now empty. The thread is doing whatever it does with the element it acquired on some CPU.
    Thread 2 attempts to dequeue an element, but finds the queue to be empty when checked under the mutex, calls pthread_cond_wait, and blocks in the call awaiting signal/broadcast.
    Thread 3 obtains the mutex, inserts a new element into the queue, notifies the condition variable, and releases the lock.
    In response to the notification from thread 3, thread 2, which was waiting on the condition, is scheduled to run.
    However before thread 2 manages to get on the CPU and grab the queue lock, thread 1 completes its current task, and returns to the queue for more work. 
It obtains the queue lock, checks the predicate, and finds that there is work in the queue. It proceeds to dequeue the item that thread 3 inserted, 
releases the lock, and does whatever it does with the item that thread 3 enqueued.
    Thread 2 now gets on a CPU and obtains the lock, but when it checks the predicate, it finds that the queue is empty. Thread 1 'stole' the item, 
so the wakeup appears to be spurious. Thread 2 needs to wait on the condition again.

So since you already always need to check the predicate under a loop, it makes no difference if the underlying condition variables can have other sorts of spurious wakeups.

起码有两种类型的Spurious wakeups:
一种是阻塞在pthread_cond_wait()的线程在没有调用signal和broadcast,即条件未达成就返回了;
第二种是阻塞在pthread_cond_wait()的线程因为调用了signal和broadcast而返回,但是当线程获取了Mutex锁后发现前面说的Boolean变量已经变为false。

即使condition variables在实现的时候将第一种情况避免了,但是第二种情况也依然会发生,特别是在线程数超过2个以上时。

举个例子,容易明白,考虑一个生产者消费者的队列,三个线程共享这个队列,队列中如果有元素,则里面有个Boolean变量为true,否则为false。

线程1从队列中取出一个元素,释放了Mutex,这时候队列为空,线程1去操作取出来的元素;
线程2也想从队列中取出元素,首先获取Mutex,但是发现为空,这时候它调用pthread_cond_wait,阻塞(阻塞时,Mutex已经释放);
线程3获取了Mutex锁,然后往队列中插入一个元素,然后调用broadcase通知线程2条件达到,然后释放Mutex锁;
线程2在收到线程3的广播后发现条件达成,准备执行。但是在线程2准备去获取cpu,获取Mutex锁之前,线程1重新获取了Mutex锁,然后发现此时队列中有元素,所以就直接取出,去操作,然后释放Mutex锁;
最终线程2获取了Mutex锁,wait返回,准备执行时发现这时候队列是空的,线程1偷了它的元素,所以这个返回就是spurious的,线程2需要重新wait。
在我认为其实就是因为wait()从收到signal或者broadcast到wait()返回要执行好几步,但是这时候不是原子操作,Mutex锁会被其他线程又抢占了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值