互斥量
1.
概述
Mutex是"mutual exclusion"的缩写,互斥量是完成线程同步和在并发操作中保护共享数据的一种主要方法。
- 互斥量在保护共享数据资源时的行为类似于"锁"。在多线程中,任何时候只有一个线程可以持有互斥量。因此,即使有多个线程尝试去持有某个互斥量,只有一个线程可以成功。多个线程必须轮流访问被保护的共享数据。
- 互斥量可以用来避免竞争条件。下图是一个涉及到银行业务的竞争条件。
-
-
Thread 1 Thread 2 Balance Read balance: $1000 $1000 Read balance: $1000 $1000 Deposit $200 $1000 Deposit $200 $1000 Update balance $1000+$200 $1200 Update balance $1000+$200 $1200
-
- 在上面的例子中,当一个线程在读写共享数据时,应该用一个互斥量来锁住"Balance".
- 互斥量常见的一个应用是在更新全局变量时,被更新的全局变量属于临界区。
- 在使用互斥量时,一般有以下几步:
(2) 多个线程尝试持有该互斥量。
(3) 第(2)步中,只有一个线程成功持有该互斥量。
(4) 持有互斥锁的线程执行指令。
(5) 释放互斥锁。
(6) 另外一个线程获取互斥锁,并执行一些操作。
(7) 销毁互斥锁。
- 当多个线程竞争一个互斥锁时,失败的线程会被阻塞。(使用trylock函数可以避免阻塞)
- 保护共享数据时,程序员应该保证每个应该使用互斥锁的线程使用互斥锁。例如:如果有四个线程都要更新某个全局变量,只有一个线程使用互斥锁,数据也会被毁坏。
- 函数
-
-
pthread_mutex_init (mutex,attr) pthread_mutex_destroy (mutex)
pthread_mutexattr_init (attr)
pthread_mutexattr_destroy (attr)
-
- 用法
a. 静态初始化。在声明时初始化。 pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
b. 动态初始化。 使用pthread_mutex_init()来初始化。
(2) attr属性用来建立互斥锁的性质,必须是pthread_mutexattr_t类型, Pthread标准定义了三种可选的mutex属性:
a. 协议:指定用来避免优先级反转的协议。
b. 优先级上限:指定互斥锁的优先级上限。
c. 进程共享:指定互斥锁的进程共享。
(3) 函数pthread_mutexattr_init()和pthread_mutexattr_destory() 用来创建和销毁互斥锁属性。
(4) pthread_mutex_destory()函数用来释放互斥锁。
3. 互斥锁操作:加锁及解锁
- 函数
-
-
pthread_mutex_lock (mutex) pthread_mutex_trylock (mutex)
pthread_mutex_unlock (mutex)
-
- 用法
(2) pthread_mutex_trylock()函数也用来给互斥量加锁,如果该互斥量已经被其他线程加锁,该函数会返回"busy"信息,这个函数一般用在避免死锁时,例如优先级翻转问题。
(3) pthread_mutex_unlock()函数用来解锁互斥量。 以下情况回返回错误:
a. 如果互斥量已经被解锁。
b. 互斥锁被其他线程所有。
(4) 互斥锁类似于多个线程之间都遵守的约定,依赖于程序开发者正确的加锁及解锁。
Question: 当多个线程在等待获取锁时, 哪个线程会最先获取?
Answer:在没有线程优先级调度策略时,互斥锁的分配依赖于native system scheduler,比较随机。
条件变量
1. 概述
- 条件变量提供多线程间同步的另一种方式。互斥锁通过控制线程读写数据来实现同步,而条件变量 通过数据的实际值来实现同步。
- 在没有竞争变量的条件下,开发者需要使得线程不停的轮询,来检查条件是否满足。这种机制非常耗费资源,可以使用条件变量来代替这种机制。
- 条件变量一般和互斥锁一起使用。
- 使用条件变量的典型步骤如下:
2. 创建和销毁条件变量
- 函数
pthread_cond_init (condition,attr) pthread_cond_destroy (condition) pthread_condattr_init (attr)
pthread_condattr_destroy (attr)
|
- 用法
(1) 条件变量必须声明为pthread_cond_t类型,在使用之前必须初始化。 有两种方式来初始化条件变量:
a. 静态初始化。
pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;
b. 动态初始化。使用函数pthread_cond_init()来初始化,条件变量的ID通过条件参数返回给调用线程,改函数可以设置条件变量属性。
(2) 可选参数attr用来设置条件变量的属性。只有一种属性:process_shared, 允许条件变量在其他进程可见。
(3) pthread_condattr_init(),pthread_condattr_destroy()函数用来创建和销毁条件变量属性。
(4) pthread_cond_destroy()函数用来释放条件变量。
3. 等待和唤醒条件变量
- 函数
pthread_cond_wait (condition,mutex) pthread_cond_signal (condition) pthread_cond_broadcast (condition) |
- 用法
(1) pthread_cond_wait()会阻塞调用该函数的线程直到收到条件变量满足的信号。该函数应该在mutex locked之后被调用,在等待阶段,它会自动释放互斥锁,在收到信号,线程被唤醒后,mutex会自动加 锁。最后由开发人员负责释放互斥锁。
建议:用WHILE循环替换IF语句来检测条件变量,可以避免一些潜在的问题:
a. 如果有多个线程在等待同一个唤醒信号,会轮流获取互斥锁,其中每个线程都可以改变条件变量。
b. 当程序失败时,线程收到错误信号。
c. 线程库允许发出假的唤醒信号。
(2) pthread_cond_signal()函数用来发出信号唤醒另外一个正在等待条件变量的线程。应该在mutex lock之后调用。
(3) 如果有多个线程阻塞在等待状态,用函数pthread_cond_broadcast()来代替pthread_cond_signal()来唤醒线程。
(4) 在调用pthread_cond_wait()之前调用pthread_cond_signal()是逻辑错误。
正确的给互斥量加锁及解锁在多线程中是很关键的:
a. 在调用pthread_cond_wait()之前未能正确加锁该线程不会被阻塞。
b. 在调用pthread_cond_signal()之后,如果没有正确的解锁,会使得满则竞争条件的线程继续阻塞。