七、Linux下线程的同步与互斥基本概念

线程的同步与互斥基本概念

一、案例的引出

在这里插入图片描述
银行卡有主卡和副卡之分,假如男孩、女孩同一时间、同一时刻取钱,可以看成两个线程。两个线程对同一银行卡同一资源进行操作,会导致的问题。

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;

    return account;
}

/** 销毁账户 */
void atm_account_Destroy(atm_Account *account)
{
    if(NULL == account){
        return ;
    }

    free(account);
}

/** 取款: 成功,则返回取款金额 */
double atm_account_Withdraw(atm_Account *account, double amt)
{
    if(NULL == account) {
        return 0.0;
    }

    if(amt < 0 || amt > account->balance) {
        return 0.0;
    }

    double balance_tmp = account->balance;
    sleep(1);
    balance_tmp -= amt;
    account->balance = balance_tmp;

    return amt;
}

/** 存款: 返回存款的金额 */
double atm_account_Desposit(atm_Account *account, double amt)
{
    if(NULL == account){
        return 0.0;
    }
    if(amt < 0){
        return 0.0;
    }

    double balance_tmp = account->balance;
    sleep(1);
    balance_tmp += amt;
    account->balance = balance_tmp;

    return amt;
}

/** 查看账户余额 */
double atm_account_BalanceGet(atm_Account *account)
{
    if(NULL == account){
        return 0.0;
    }

    double balance_tmp = account->balance;
    return balance_tmp;
}

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>

/** 账户信息 */
typedef struct {
    int         code;       ///< 银行账户的编码
    double      balance;    ///< 账户余额
}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_handler.c

#include "atm_handler.h"

/** 定义取款操作的线程运行函数 */
void *atm_handler_Withdraw(void *arg)
{
    atm_handler_t *handler_tmp = (atm_handler_t *)arg;
    double amt = atm_account_Withdraw(handler_tmp->account, handler_tmp->amt);

    printf("%10s(0x%lu) withdraw %f from account %d\n", handler_tmp->name, pthread_self(), amt, handler_tmp->account->code);

   return (void *)0;
}

/** 定义存款操作的线程运行函数 */
void *atm_handler_Desposit(void *arg)
{
    atm_handler_t *handler_tmp = (atm_handler_t *)arg;
    double amt = atm_account_Desposit(handler_tmp->account, handler_tmp->amt);

    printf("%10s(0x%lu) deposit %f from account %d\n", handler_tmp->name, pthread_self(), amt, handler_tmp->account->code);

   return (void *)0;
}

/** 定义检查银行账户的线程运行函数 */
void *atm_handler_AccountCheck(void *arg)
{
    return (void *)0;
}

/** 账户操作主函数 */
atm_error_t atm_handler_main(void)
{
    int err;
    pthread_t boy, girl;
    atm_Account *account = atm_account_Create(1000001, 10000);
    if(NULL == account){
        return ATM_ERROR_ACCOUNT_CREATE;
    }

    atm_handler_t usr1, usr2;
    strcpy(usr1.name, "boy");
    usr1.account = account;
    usr1.amt = 10000;

    strcpy(usr2.name, "girl");
    usr2.account = account;
    usr2.amt = 10000;

    /** 启动两个线程(boy 和 girl 线程)同时去操作同一个银行账户 */
    if((err = pthread_create(&boy, NULL, atm_handler_Withdraw, (void *)&usr1)) != 0) {
        perror("pthread create error");
    }

    if((err = pthread_create(&girl, NULL, atm_handler_Withdraw, (void *)&usr2)) != 0) {
        perror("pthread create error");
    }
    
    /** 主线程阻塞 */
    pthread_join(boy, NULL);
    pthread_join(girl, NULL);

    printf("account balance: %f\n", atm_account_BalanceGet(account));
    atm_account_Destroy(account);

    return ATM_ERROR_NONE;
}

atm_handler.h

#ifndef __ATM_HANDLER_H__
#define __ATM_HANDLER_H__

#include "atm_account.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef enum {
    ATM_ERROR_NONE,
    ATM_ERROR_ACCOUNT_CREATE
}atm_error_t;

/** 账户操作结构体 */
typedef struct {
    char            name[20];   ///< 操作人的姓名
    atm_Account     *account;   ///< 操作的账户
    double          amt;        ///< 操作的金额
}atm_handler_t;

extern atm_error_t atm_handler_main(void);

#endif

atm_test.c

#include "atm_handler.h"

int main(void)
{
    atm_handler_main();
    return 0;
}

Makefile

#PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
PROJECT_ROOT = $(shell pwd)
SRC_DIR = $(PROJECT_ROOT)/src
INCLUDE_DIR = $(PROJECT_ROOT)/include
OBJ_DIR = $(PROJECT_ROOT)/obj
BIN_DIR = $(PROJECT_ROOT)/bin

# 找出 src 目录下的所有 .c 文件
C_SRCS = $(wildcard $(SRC_DIR)/*.c)
# 将所有的 src 下的 .c 文件替换为 .o 文件
C_OBJS = $(patsubst %c, %o, $(C_SRCS))
TARGET = test
SHARE_LIB = libatm.a

C_SRC_MAIN = atm_test.c

CC = gcc
CCFLAGS += fPIC
LDFLAGS += -shared -fPIC
ASFLAGS +=
ARFLAGS = -crs
LIBS_FLAGS = -L$(BIN_DIR)

RM = rm -rf

CFLAGS += -Wall -g -I$(INCLUDE_DIR)
INCDIR += -I$(INCLUDE_DIR)

.PHONY: all clean test

all: $(TARGET)
        cp $(SHARE_LIB) $(BIN_DIR)
        cp $(SRC_DIR)/*.o $(OBJ_DIR)/
        $(RM) $(SHARE_LIB) $(SRC_DIR)/*.o

$(TARGET): $(SHARE_LIB)
        $(CC) $(C_SRC_MAIN) -o $(TARGET) $(CFLAGS) $(INCDIR) $(LIBS_FLAGS) -latm -lpthread

$(SHARE_LIB): $(C_OBJS)
        $(AR) $(ARFLAGS) $(SHARE_LIB) $(C_OBJS)
        cp $(SHARE_LIB) $(BIN_DIR)

$(C_OBJS) : %.o:%.c
        $(CC) -c $(CFLAGS) $(INCDIR) -o $@ $< -lpthread

clean:
        $(RM) mshell 
        $(RM) $(SHARE_LIB) 
        $(RM) $(OBJ_DIR)/$(OBJS)/*.o 
        $(RM) $(SRC_DIR)/*.o

案例的运行结果可知,男孩女孩同时对一银行账户操作,会出现都取出10000.00的现象,余额总共才10000.00。这样的程序设计必定存在问题。存在对共享资源的访问问题。
在这里插入图片描述
使用GCC命令进行编译:把atm_test.c移至src文件夹,这样比较简单,不需要太复杂的Makefile。

gcc -o bin/account_test -Iinclude src/atm_account.c src/atm_test.c src/atm_handler.c -lpthread

二、线程的互斥

多个线程之间有共享资源(shared resource)时会出现互斥现象。
线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
假设有若干线程共享某个变量,而且都对变量有修改。如果它们之间不考虑相互协调工作,就会产生混乱。比如,线程A和B共用变量x,都对x执行增1操作。由于A和B没有协调,两线程对x的读取、修改和写入操作相互交叉,可能两个线程读取相同个x值,一个线程将修改后的x新值写入到x后,另一个线程也把自己对x修改后的新值写入到x。这样,x只记录后一个线程的修改作用。

临界段:多线程互斥使用共享资源的程序段,在操作系统中称为临界段。临界段是一种加锁的机制,与多线程共享资源有关
临界段的作用是在任何时刻一个共享资源只能供一个线程使用。当资源未被占用,线程可以进入处理这个资源的临界段,从而得到该资源的使用权;当线程执行完毕,便退出临界段。如果一个线程已进入某个共享资源,并且还没有使用结束,其他线程必须等待。
多个线程中涉及到同一个临界资源的临界区称为相关临界区。线程进入临界区的调度原则是:

  1. 如果有若干线程要求进入空闲的临界区,一次仅允许一个线程进入。
  2. 任何时候,处于临界区内的线程不可多于一个。如已有线程进入自己的临界区,则其它所有试图进入临界区的线程必须等待。
  3. 进入临界区的线程要在有限时间内退出,以便其它线程能及时进入自己的临界区。
  4. 如果线程不能进入自己的临界区,则应让出CPU,避免线程出现“忙等”现象。

三、 线程的同步

多线程之间除了有互斥情况外,还有线程同步。线程同步:是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
可以这样理解:当线程A使用某个对象,而此对象又需要线程B修改后才能符合本线程的需要,此时线程A就要等待线程B完成修改工作。这种线程相互等待称为线程的同步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值