第9章 线程编程_线程同步1:互斥锁

Posted 浅墨浓香

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第9章 线程编程_线程同步1:互斥锁相关的知识,希望对你有一定的参考价值。

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;
}
/*输出结果:
 [[email protected]]# 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;
}
/*输出结果:
[[email protected]]# bin/lock_type error  //检错互斥锁  
first lock success
second lock failure
[[email protected]]# bin/lock_type recursive //递归互斥锁
first lock success
second lock success
[[email protected]]# bin/lock_type normal   //标准互斥锁
first lock success
^C                     //阻塞
[[email protected]]# 
*/

 

以上是关于第9章 线程编程_线程同步1:互斥锁的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程之Linux环境下的多线程

高并发基石多线程守护线程线程安全线程同步互斥锁

并发编程 线程互斥

9 并发编程-(线程)-守护线程&互斥锁

Unix网络编程-同步

Linux 线程编程2.0——线程同步-互斥锁