一、线程的互斥锁
为什么使用线程锁?
在多线程应用程序中,当多个线程共享相同的内存时,如同时访问一个变量时,需要确保每个线程看到一致的数据视图,即保证所有线程对数据的修改是一致的。
如下两种情况不存在不一致的问题:
- 每个线程使用的变量都是其他线程不会读取和修改的
- 变量是只读的
当一个线程在修改变量的值时,其他线程在读取这个变量时可能会得到一个不一致的值。
一个典型的例子是,在一个多线程程序中,两个及以上个线程对同一个变量i执行i++操作,结果得到的值并不如顺序执行所预期的那样。这就是线程间不同步的一个例子。
可以用程序修改变量值时所经历的三个步骤解释这个现象:
- 从内存单元读入寄存器
- 在寄存器中对变量操作(加/减1)
- 把新值写回到内存单元
不能预期以上三步骤在一个总线周期内完成,所以也就不能指望多线程程序如预期那样运行。
1. 互斥量
多线程程序中可能会存在数据不一致的情况,那么如何保证数据一致呢?可以考虑同一时间只有一个线程访问数据。互斥量(mutex)就是一把锁。
多个线程只有一把锁一个钥匙,谁上的锁就只有谁能开锁。当一个线程要访问一个共享变量时,先用锁把变量锁住,然后再操作,操作完了之后再释放掉锁,完成。
当另一个线程也要访问这个变量时,发现这个变量被锁住了,无法访问,它就会一直等待,直到锁没了,它再给这个变量上个锁,然后使用,使用完了释放锁,以此进行。
这个即使有多个线程同时访问这个变量,也好象是对这个变量的操作是顺序进行的。
2. 互斥量的初始化
互斥锁(mutex)是一种简单的加锁的方法来控制对共享资源的访问。
- 在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行访问。
- 若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。
a. 静态分配
pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER;
b. 动态分配
互斥变量使用特定的数据类型:pthread_mutex_t,使用互斥量前要先初始化,使用的函数如下:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
其中 pthread_mutex_t 是一个表示互斥量的联合体类型,函数的第一个参数是要初始化的互斥量,第二个参数是要设置的互斥量的属性,参数二为空则表示使用默认的属性。函数初始化互斥锁成功返回0,否则返回非0。
静态初始化的互斥量不需要销毁,而对于动态初始化的互斥量则需要销毁,其接口函数如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数为要销毁的互斥量,上面的初始化和销毁的函数执行成功返回 0,出错返回错误码。
对互斥量加锁解锁的函数如下:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthreadd_mutex_t *mutex);
第一个函数 pthread_mutex_lock 对互斥量阻塞式加锁,如果互斥量已经上锁,则调用线程将则阻塞式等待,直到互斥量被解锁。pthread_mutex_unlock 用于解锁,如果不希望阻塞式的加锁,则使用第二个函数 pthread_mutex_trylock ,该函数尝试对互斥量加锁,如果互斥量未被上锁,则将互斥量锁住,否则,该函数返回 EBUSY。
3. 案例改进
atm_account.h
#ifndef __ATM_ACCOUNT_H__
#define __ATM_ACCOUNT_H__
#include <math.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/** 账户信息 */
typedef struct {
int code; ///< 银行账户的编码
double balance; ///< 账户余额
/*
*建议互斥锁用来锁定一个账户(共享资源),和账户(共享资源)绑定在一起。
*尽量不要设置成全局变量,否则会出现一把锁上几百个账户,导致并发性能降低。
*/
pthread_mutex_t mutex; ///< 定义一把互斥锁,用来对多线程操作的银行账户(共享资源)进行加锁
}atm_Account;
/** 创建账户 */
extern atm_Account *atm_account_Create(int code, double balance);
/** 销毁账户 */
extern void atm_account_Destroy(atm_Account *account);
/** 取款 */
extern double atm_account_Withdraw(atm_Account *account, double amt);
/** 存款 */
extern double atm_account_Desposit(atm_Account *account, double amt);
/** 查看账户余额 */
extern double atm_account_BalanceGet(atm_Account *account);
#endif
atm_account.c
#include "atm_account.h"
/** 创建账户 */
atm_Account *atm_account_Create(int code, double balance)
{
atm_Account *account = (atm_Account *)malloc(sizeof(atm_Account));
if(NULL == account) {
return NULL;
}
account->code = code;
account->balance = balance;
/** 对互斥锁进行初始化 */
pthread_mutex_init(&account->mutex, NULL);
return account;
}
/** 销毁账户 */
void atm_account_Destroy(atm_Account *account)
{
if(NULL == account){
return ;
}
pthread_mutex_destroy(&account->mutex);
free(account);
}
/** 取款: 成功,则返回取款金额 */
double atm_account_Withdraw(atm_Account *account, double amt)
{
if(NULL == account) {
return 0.0;
}
/** 对共享资源(账户进行加锁) */
pthread_mutex_lock(&account->mutex);
if(amt < 0 || amt > account->balance) {
pthread_mutex_unlock(&account->mutex);
return 0.0;
}
double balance_tmp = account->balance;
sleep(1);
balance_tmp -= amt;
account->balance = balance_tmp;
pthread_mutex_unlock(&account->mutex);
return amt;
}
/** 存款: 返回存款的金额 */
double atm_account_Desposit(atm_Account *account, double amt)
{
if(NULL == account){
return 0.0;
}
/** 对共享资源(账户进行加锁) */
pthread_mutex_lock(&account->mutex);
if(amt < 0){
pthread_mutex_unlock(&account->mutex);
return 0.0;
}
double balance_tmp = account->balance;
sleep(1);
balance_tmp += amt;
account->balance = balance_tmp;
pthread_mutex_unlock(&account->mutex);
return amt;
}
/** 查看账户余额 */
double atm_account_BalanceGet(atm_Account *account)
{
if(NULL == account){
return 0.0;
}
/** 对共享资源(账户进行加锁) */
pthread_mutex_lock(&account->mutex);
double balance_tmp = account->balance;
pthread_mutex_unlock(&account->mutex);
return balance_tmp;
}
从运行的结果可知,对共享资源加锁后,对资源的访问就存在互斥的关系,将不会出现上述错误,男孩操作账户对共享资源操作时,女孩是不能操作共享资源的。