5. 线程的互斥和同步
5.1 同步和互斥的概念
(1)线程同步:是一个宏观概念,在微观上包含线程的相互排斥和线程的先后执行的约束问题。解决同步方式一般采用条件变量和信号量。
(2)线程互斥:线程执行的相互排斥(注意,它不关心线程间执行的先后顺序!)。解决互斥一般使用互斥锁、读写锁和信号量。
【编程实验】银行ATM(线程不安全的例子)
//account.h
#ifndef __ACCOUNT_H__ #define __ACCOUNT_H__ typedef struct { int code; //帐号 double balance; //余额 }Account; //创建账户 extern Account* create_account(int code, double balance); //销毁帐户 extern void destroy_account(Account* a); //取款 extern double withdraw(Account* a, double amt); //amt == amount //存款 extern double deposit(Account* a, double amt); //查看帐户余额 extern double get_balance(Account* a); #endif //__ACCOUNT_H__
//account.c
#include "account.h" #include <malloc.h> #include <string.h> #include <assert.h> //创建账户 Account* create_account(int code, double balance) { Account* ret = (Account*)malloc(sizeof(Account)); assert(ret != NULL); ret->code = code; ret->balance = balance; return ret; } //销毁帐户 void destroy_account(Account* a) { assert( a != NULL); free(a); } //取款 double withdraw(Account* a, double amt) //amt == amount { assert(a != NULL); if((amt < 0) || (amt > a->balance)){ return 0.0; } double balance = a->balance; //先取余额 sleep(1); //为模拟多线程下可能出现的问题 balance -= amt; a->balance = balance; //更新余额。在读取余额和更新余额之间有 //故意留出“时间窗口”。 return amt; } //存款 double deposit(Account* a, double amt) { assert(a != NULL); if(amt < 0){ return 0.0; } double balance = a->balance; //先取余额 sleep(1); //为模拟多线程下可能出现的问题 balance += amt; a->balance = balance; //更新余额。 return amt; } //查看帐户余额 double get_balance(Account* a) { assert(a != NULL); double balance = a->balance; return balance; }
//account_test.c
#include "account.h" #include <stdio.h> #include <stdlib.h> #include <pthread.h> //#include <string.h> //for strcpy typedef struct { char name[20]; Account* account; double amt; }OperArg; //定义取款操作的线程函数 void* withdraw_fn(void* arg) { OperArg* oa = (OperArg*)arg; double amt = withdraw(oa->account, oa->amt); printf("%s(0x%lx) withdraw %f from account(%d)\n", oa->name,pthread_self(), amt, oa->account->code); return (void*)0; } //定义存款操作的线程函数 void* deposit_fn(void* arg) { OperArg* oa = (OperArg*)arg; double amt = deposit(oa->account, oa->amt); printf("%s(0x%lx) deposit %f from account(%d)\n", oa->name,pthread_self(), amt, oa->account->code); return (void*)0; } int main(void) { int err = 0; pthread_t boy, girl; Account* a = create_account(100001, 10000); OperArg o1 = {"boy", a, 10000}; //strcpy(o1.name, "boy"); OperArg o2 = {"girl", a, 10000}; //启动两个子线程(boy和girl线程)同时去操作同一个银行帐户 if((err = pthread_create(&boy, NULL, withdraw_fn, (void*)&o1)) != 0){ perror("pthread_create error"); } if((err = pthread_create(&girl, NULL, withdraw_fn, (void*)&o2)) != 0){ perror("pthread_create error"); } pthread_join(boy, NULL); pthread_join(girl, NULL); //查看余额 printf("account balance: %f\n", get_balance(a)); destroy_account(a); return 0; } /*输出结果: [root@bogon]# bin/account_test girl(0xb6d5eb70) withdraw 10000.000000 from account(100001) boy(0xb775fb70) withdraw 10000.000000 from account(100001) //错误,余额共1万,但取了2万 account balance: 0.000000 */
5.2 互斥锁
5.2.1 互斥锁简介
(1)互斥锁(mutex)是用一种简单的加锁方法来控制对共享资源的访问。在同一时刻只能有一个线程拥有某个互斥锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个己经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。
(2)互斥锁数据类型:pthread_mutex_t
5.2.2 互斥锁的使用
(1)创建和销毁互斥锁
头文件 |
#include <pthread.h> |
函数 |
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutex_attr_t* mutex); int pthread_mutex_destroy(pthread_mutex_t* mutex); |
返回值 |
成功返回0,否则返回错误编号 |
参数 |
(1)mutex:互斥锁 (2)mutexattr:互斥锁创建方式 ①PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁 ②PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁 ③PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁 |
(2)上锁和解锁
头文件 |
#include <pthread.h> |
函数 |
int pthread_mutex_lock(pthread_mutex_t* mutex); //上锁,拿不到锁阻塞 int pthread_mutex_trylock(pthread_mutex_t* mutex); //上锁,拿不到锁返回出错信息 int pthread_mutex_unlock(pthread_mutex_t* mutex);//释放锁 |
返回值 |
成功返回0,否则返回错误编号 |
参数 |
mutex:互斥锁 |
【编程实验】银行帐号ATM(利用互斥锁实现线程安全的操作)
//account.h
#ifndef __ACCOUNT_H__ #define __ACCOUNT_H__ #include <pthread.h> typedef struct { int code; //帐号 double balance; //余额 //使用互斥锁,用来对多线程操作的银行帐户(共享资源)进行加锁保护。 /* *建议互斥锁和一个帐户绑定。尽量不设置成全局变量,否则可能出现用一 *个锁去锁定多个帐户,从而导致并发性能降低。 */ pthread_mutex_t mutex; }Account; //创建账户 extern Account* create_account(int code, double balance); //销毁帐户 extern void destroy_account(Account* a); //取款 extern double withdraw(Account* a, double amt); //amt == amount //存款 extern double deposit(Account* a, double amt); //查看帐户余额 extern double get_balance(Account* a); #endif //__ACCOUNT_H__
//account.c
#include "account.h" #include <malloc.h> #include <string.h> #include <assert.h> //创建账户 Account* create_account(int code, double balance) { Account* ret = (Account*)malloc(sizeof(Account)); assert(ret != NULL); ret->code = code; ret->balance = balance; //对互斥锁进行初始化 pthread_mutex_init(&ret->mutex, NULL); return ret; } //销毁帐户 void destroy_account(Account* a) { assert( a != NULL); //销毁互斥锁 pthread_mutex_destroy(&a->mutex); free(a); } //取款 double withdraw(Account* a, double amt) //amt == amount { assert(a != NULL); pthread_mutex_lock(&a->mutex); //对共享资源加锁 if((amt < 0) || (amt > a->balance)){ //释放互斥锁 pthread_mutex_unlock(&a->mutex); return 0.0; } double balance = a->balance; //先取余额 sleep(1); //为模拟多线程下可能出现的问题 balance -= amt; a->balance = balance; //更新余额。在读取余额和更新余额之间有 //故意留出“时间窗口”。 pthread_mutex_unlock(&a->mutex); return amt; } //存款 double deposit(Account* a, double amt) { assert(a != NULL); if(amt < 0){ return 0.0; } pthread_mutex_lock(&a->mutex); double balance = a->balance; //先取余额 sleep(1); //为模拟多线程下可能出现的问题 balance += amt; a->balance = balance; //更新余额。 pthread_mutex_unlock(&a->mutex); return amt; } //查看帐户余额 double get_balance(Account* a) { assert(a != NULL); pthread_mutex_lock(&a->mutex); double balance = a->balance; pthread_mutex_unlock(&a->mutex); return balance; }
//account_test.c
#include "account.h" #include <stdio.h> #include <stdlib.h> #include <pthread.h> //#include <string.h> //for strcpy typedef struct { char name[20]; Account* account; double amt; }OperArg; //定义取款操作的线程函数 void* withdraw_fn(void* arg) { OperArg* oa = (OperArg*)arg; double amt = withdraw(oa->account, oa->amt); printf("%s(0x%lx) withdraw %f from account(%d)\n", oa->name,pthread_self(), amt, oa->account->code); return (void*)0; } //定义存款操作的线程函数 void* deposit_fn(void* arg) { OperArg* oa = (OperArg*)arg; double amt = deposit(oa->account, oa->amt); printf("%s(0x%lx) deposit %f from account(%d)\n", oa->name,pthread_self(), amt, oa->account->code); return (void*)0; } int main(void) { int err = 0; pthread_t boy, girl; Account* a = create_account(100001, 10000); OperArg o1 = {"boy", a, 10000}; //strcpy(o1.name, "boy"); OperArg o2 = {"girl", a, 10000}; //启动两个子线程(boy和girl线程)同时去操作同一个银行帐户 if((err = pthread_create(&boy, NULL, withdraw_fn, (void*)&o1)) != 0){ perror("pthread_create error"); } if((err = pthread_create(&girl, NULL, withdraw_fn, (void*)&o2)) != 0){ perror("pthread_create error"); } pthread_join(boy, NULL); pthread_join(girl, NULL); //查看余额 printf("account balance: %f\n", get_balance(a)); destroy_account(a); return 0; }
5.2.3 互斥锁的属性
(1)互斥锁属性的创建和销毁
头文件 |
#include <pthread.h> |
函数 |
int pthread_mutexattr_init(pthread_mutexattr_t* attr); int pthread_mutexattr_destroy(pthread_mutexattr_t* attr); |
返回值 |
成功返回0,否则返回错误编号 |
参数 |
attr:互斥锁属性 |
(2)互斥锁的进程共享属性
头文件 |
#include <pthread.h> |
函数 |
int pthread_mutexattr_getpshared(const pthread_mutexattr_t* attr, int* pshared);//获取互斥锁的共享属性,结果存在入pshared中 int pthread_mutexattr_setpshared(pthread_mutexattr_t* attr, int pshared); //设置互斥锁的进程共享属性 |
返回值 |
成功返回0,否则返回错误编号 |
参数 |
(1)attr:互斥锁属性 (2)pshared:进程共享属性: ①PTHREAD_PROCESS_PRIVATE(默认情况):锁只能用于一个进程内部的两个线程进行互斥 ②PTHREAD_PROCESS_SHARED:可用于两个不同进程中的线程进行互斥 |
(3)互斥锁的类型
头文件 |
#include <pthread.h> |
函数 |
int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type);//获取互斥锁的类型,结果存在入type中 int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type); //设置互斥锁的类型 |
返回值 |
成功返回0,否则返回错误编号 |
参数 |
(1)attr:互斥锁属性 (2)type:互斥锁的类型: ①标准互斥锁:PTHREAD_MUTEX_NORMAL:第1次上锁成功,第2次上锁会阻塞。 ②递归互斥锁:PTHREAD_MUTEX_RECURSIVE:第1次上锁成功,第2次以后上锁还是成功,内部计数。 ③检错互斥锁:PTHREAD_MUTEX_ERRORCHECK:第1次上锁成功,第2次上锁会出错。 ④默认互斥锁:PROCESS_MUTEX_DEFAULT:(同标准互斥锁) |
【编程实验】不同类型的互斥锁
//lock_type.c
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /*不同互斥锁的多次上锁操作*/ int main(int argc, char* argv[]) { pthread_mutex_t mutex; if(argc < 2){ printf("-usage: %s [error|normal|recursive]\n", argv[0]); exit(1); } //定义互斥锁属性 pthread_mutexattr_t mutexattr; //初始化互斥锁属性 pthread_mutexattr_init(&mutexattr); if(!strcmp(argv[1], "error")){ //设置互斥锁类型 pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK); }else if(!strcmp(argv[1], "normal")){ pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_NORMAL); }else if(!strcmp(argv[1], "recursive")){ pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE); } //初始化互斥锁 pthread_mutex_init(&mutex, &mutexattr); //第1次上锁 if(pthread_mutex_lock(&mutex) != 0){ printf("first lock failure\n"); }else{ printf("first lock success\n"); } //第2次上锁 if(pthread_mutex_lock(&mutex) != 0){ printf("second lock failure\n"); }else{ printf("second lock success\n"); } pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex); pthread_mutexattr_destroy(&mutexattr); pthread_mutex_destroy(&mutex); return 0; } /*输出结果: [root@bogon]# bin/lock_type error //检错互斥锁 first lock success second lock failure [root@bogon]# bin/lock_type recursive //递归互斥锁 first lock success second lock success [root@bogon]# bin/lock_type normal //标准互斥锁 first lock success ^C //阻塞 [root@bogon]# */