Linux学习_线程的互斥
Posted Leslie X徐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux学习_线程的互斥相关的知识,希望对你有一定的参考价值。
线程的同步和互斥
同步和互斥的概念
- 线程同步
- 线程同步是一个宏观的概念,在微观上包含线程的相互排斥(注意和线程互斥的不一样)和线程先后执行的约束问题。
- 解决同步方式
- 条件变量
- 线程信号量
- 线程互斥
- 线程执行是相互排斥的,一个线程在操作共享资源时,其他线程是处于阻塞状态或等待状态,不可以操作共享资源。
- 和线程同步相互排斥的区别是:线程互斥不考虑线程先后执行的约束问题。
- 解决互斥方式
- 互斥锁
- 读写锁
- 线程信号量
保证同一时间操作共享资源的线程只有一个。
- ATM线程冲突示例
创建boy和girl线程同时对银行账户进行操作
主函数:
typedef struct
{
int code;
double balance;
}Account,*pAccount;
//取款金额amt
extern double withdraw(pAccount a, double amt)
{
if(amt<0 || amt> a->balance) return 0.0;
double balance = a->balance;
sleep(1);//模拟银行连接互联网取钱的延时
balance -= amt;
a->balance = balance;
return amt; //成功则返回取款金额
}
//------------------------------------------------------------------------
/*
* account_test.c
* 创建boy和girl线程同时对银行账户进行操作
*/
//头文件
//--------------------------------
#include "account.h"
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
//--------------------------------
//结构体
//--------------------------------
typedef struct
{
char name[20];
pAccount account;
double amt; //存取款金额
}OperArg;
//--------------------------------
//定义取款操作的线程运行函数
//--------------------------------
void* withdraw_fn(void* arg)
{
OperArg *oa = (OperArg*)arg;
double amt = withdraw(oa->account, oa->amt);
printf("%8s(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("%8s(0x%lx) deposit %f from account %d\\n",\\
oa->name, pthread_self(), amt, oa->account->code);
return (void*)0;
}
//--------------------------------
//定义检查银行账户的线程运行函数
//--------------------------------
void* check_fn(void* arg)
{
return (void*)0;
}
//--------------------------------
//--------------------------------
//主函数
//--------------------------------
int main(void)
{
int err;
pthread_t boy, girl;
pAccount a = create_account(100001, 10000);
OperArg o1, o2; //o1,o2分别代表操作的两个用户
strcpy(o1.name, "boy");
o1.account = a;
o1.amt = 10000;
strcpy(o2.name, "girl");
o2.account = a;
o2.amt = 10000;
//启动两个子线程(boy和girl)
//同时操作同一个银行账户
err = pthread_create(&boy, NULL,withdraw_fn, (void*)&o1);
if(err != 0) perror("pthread create error");
err = pthread_create(&girl, NULL,withdraw_fn, (void*)&o2);
if(err != 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;
}
输出:
girl(0xb657e460) withdraw 10000.000000 from account 100001
boy(0xb6d7f460) withdraw 10000.000000 from account 100001
account balance: 0.000000
男生女生同时取款,都取到了10000元,相当于两人一共取了20000,这是不对的。
此时线程不安全。
分析:boy girl 两者共同调用了withdraw函数,在栈中都有一个空间,girl在boy延时睡眠时进来,此时账户里余额并未改变,于是两人得到的balance都为10000。
线程互斥——互斥锁(互斥量)
- 概念:
- 互斥锁(mutex)是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则线程挂起,直到上锁的线程释放互斥锁为止。
- 互斥锁数据类型
- pthread_mutex_t
- 互斥锁的创建和销毁
#include <pthread.h>
int pthread_mutex_init(
pthread_mutex_t* restrict mutex,
const pthread_mutexattr_t* mutexattr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
- 返回:成功返回0,否则返回错误编号。
- 参数:
- mutex:互斥锁
- mutexattr:互斥锁创建方式(一般写NULL,默认互斥锁类型)
- PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
- PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁
- PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁
- 互斥锁上锁和解锁
#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,否则返回错误编号。
对ATM示例进行加锁
/*account.c
*/
typedef struct
{
int code;
double balance;
//定义一把互斥锁,用来对多个线程操作的银行账户(共享资源)进行加锁(保护)
//建议一把互斥锁绑定一个账户,尽量不设置成全局变量,否则可能出现一把锁去锁几百个银行账户导致并发性降低
pthread_mutex_t mutex;
}Account,*pAccount;
#include "account.h"
#include <malloc.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
//创建账户
extern pAccount create_account(int code, double balance)
{
pAccount a = (pAccount)malloc(sizeof(Account));
assert(a != NULL);
a->code = code;
a->balance = balance;
//对互斥锁进行初始化
pthread_mutex_init(&a->mutex, NULL);
return a;
}
//销毁账户
extern void destroy_account(pAccount a)
{
assert(a != NULL);
//销毁互斥锁
pthread_mutex_destroy(&a->mutex);
free(a);
}
//取款金额amt
extern double withdraw(pAccount a, double amt)
{
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; //成功则返回取款金额
}
//存款
extern double deposit(pAccount a, double amt)
{
assert(a != NULL);
//对共享资源(账户)加锁
pthread_mutex_lock(&a->mutex);
if(amt<0){
//释放互斥锁
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;
}
//查看账户余额
extern double get_balance(pAccount a)
{
assert(a != NULL);
//对共享资源(账户)加锁
pthread_mutex_lock(&a->mutex);
double balance = a->balance;
//释放互斥锁
pthread_mutex_unlock(&a->mutex);
return balance;
}
给每个线程加上锁,再运行主函数
输出:
boy(0xb6dc5460) withdraw 10000.000000 from account 100001
girl(0xb65c4460) withdraw 0.000000 from account 100001
account balance: 0.000000
- 互斥锁属性创建和销毁
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
- 返回:成功返回0,否则返回错误编号
- 参数:attr:互斥锁属性
- 互斥锁进程共享属性操作
#include <pthread.h>
int pthread_mutexattr_getpshared(
const pthread_mutexattr_t* restrict attr,
int* restrict pshared);
int pthread_mutexattr_setpshared(
pthread_mutexattr_t* attr,
int pshared);
- 返回:成功返回0,否则返回错误编号
- 参数:
- attr:互斥锁属性
- pshared:进程共享属性
- PTHREAD_PROCESS_PRIVATE(默认情况)
- 锁只能用于一个进程内部的多个线程进行互斥
- PTHREAD_PROCESS_SHARED
- 锁可以用于两个不同进程中的线程进行互斥
- PTHREAD_PROCESS_PRIVATE(默认情况)
- 互斥锁类型操作
#include <pthread.h>
int pthread_mutexattr_gettype(
const pthread_mutexattr_t* restrict attr,
int* restrict type);
int pthread_mutexattr_settype(
pthread_mutexattr_t* attr,
int type);
- 返回:成功返回0,否则返回错误编号
- 参数:
- attr:互斥锁属性
- type:互斥锁类型
- 标准互斥锁:PTHREAD_MUTEX_NORMAL
- 第一次上锁成功,第二次上锁会阻塞。
- 递归互斥锁:PTHREAD_MUTEX_RECURSIVE
- 第一次上锁成功,第二次以后上锁还是成功,内部计数。
- 检错互斥锁:PTHREAD_MUTEX_ERRORCHECK
- 第一次上锁成功,第二次上锁会出错。
- 默认互斥锁:PTHREAD_MUTEX_DEFAULT(同标准互斥锁)
- 标准互斥锁:PTHREAD_MUTEX_NORMAL
示例
/*
* 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);
//check argv传入的互斥锁类型
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);
//上锁
if(pthread_mutex_lock(&mutex)!=0)
printf("lock failure\\n");
else
printf("lock success\\n");
//第二次上锁
if(pthread_mutex_lock(&mutex)!=0)
printf("lock failure\\n");
else
printf("lock success\\n");
//上两次锁,也需要释放两次
pthread_mutex_unlock(&mutex);
pthread_mutex_unlock(&mutex);
//销毁互斥锁和属性
pthread_mutexattr_destroy(&mutexattr);
pthread_mutex_destroy(&mutex);
return 0;
}
输出:
$ ./lock_type error
lock success
lock failure
$ ./lock_type recursive
lock success
lock success
$ ./lock_type normal
lock success
^C
$
线程互斥——读写锁
- 概念
- 线程使用互斥锁缺乏读并发性。
- 当读操作较多,写操作较少时,可使用读写锁提高线程读并发性。
- 读写锁数据类型
- pthread_rwlock_t
- 读写锁创建和销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,\\
const pthread_rwlockattr_t* restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
- 返回:成功返回0,否则返回错误编号
- 参数:
- rwlock:读写锁
- attr:读写锁属性
- 读写锁加锁和解锁
#include <pthread.h>
//功能:加读锁
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
//功能:加写锁
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
//功能:释放锁
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
- 返回:成功返回0,否则返回错误编号
- 参数:
- rwlock:读写锁
- 示例:
/*
* rwlock_feature.c
*
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定义读写锁
pthread_rwlock_t rwlock;
int main(int argc, char **argv)
{
if(argc<3){
printf("-usage:%s [r|w] [r|w]\\n",argv[0]);
exit(1);
}
//读写初始化
pthread_rwlock_init(&rwlock,NULL);
if(!strcmp("r",argv[1])){
//加读锁
if(pthread_rwlock_rdlock(&rwlock) != 0)
{printf("first read lock failure\\n");}
else
{printf("first read lock success\\n");}
}
if(!strcmp("w",argv[1])){
//加写锁
if(pthread_rwlock_wrlock(&rwlock) != 0)
{printf("first write lock failure\\n");}
else
{printf("first write lock success\\n");}
}
if(!strcmp("r",argv[2])){
//加读锁
if(pthread_rwlock_rdlock(&rwlock) != 0)
{printf("second read lock failure\\n");}
else
{printf("second read lock success\\n");}
}
if(!strcmp("w",argv[2])){
//加写锁
if(pthread_rwlock_wrlock(&rwlock) != 0)
{printf("second write lock failure\\n");}
else
{printf("second write lock success\\n");}
}
//释放读写锁
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
return 0;
}
输出:
$ ./rwlock_feature r r
first read lock success
second read lock success
$ ./rwlock_feature r w
first read lock success
^C
$ ./rwlock_feature w w
first write lock success
second write lock failure
$ ./rwlock_feature w r
first write lock success
second read lock failure
分析:
- 读和读:不排斥
- 读和写\\写和读:排斥(后一个线程阻塞或失败)
- 写和写:排斥
以上是关于Linux学习_线程的互斥的主要内容,如果未能解决你的问题,请参考以下文章