操作系统——同步机制及应用编程实现比较

目录

1.实验目的 

2.实验内容 

4.实验环境

5.1实验总体设计

flag 

turn

6.实验结果

6.1未采取同步控制运行


1.实验目的 

探索、理解并掌握操作系统同步机制的设计和实现机理,针对所谓的银行账户转账同步问题,构建基于 Peterson 算法的同步解决方案以及基于 Windows(或 Linux)操作系统同步机制(主要是互斥机制)的解决方案,并进行分析和比较

2.实验内容 

针对银行账户转账同步问题,分析、设计和利用 C 语言编程实现基于 Peterson 算法的同步解决方案,以及基于Windows(或 Linux)操作系统同步机制的相应解决方案,并就自编同步机制与操作系统自身同步机制的效率进行比较和分析。

3.实验要求 

同步机制及应用编程实现与比较实验功能设计要求:

1)银行账户转账同步问题的抽象及未采取同步控制情况下的编程实现;

2)基于 Peterson 算法的银行账户转账同步问题解决方案;

3)基于 Windows(或 Linux)操作系统同步机制的银行账户转账同步问题解决方案;

4Peterson 算法同步机制和 Windows(或 Linux)操作系统同步机制的效率比较。

4.实验环境

开发环境

Visual Studio

运行环境

windows

测试环境

终端(笔记本)

  1. 实验设计与实现

5.1实验总体设计

5.1.1Account类设计

受控的操作流程:通过定义的 deposit 和 withdraw 方法,类对存款和取款操作进行了控制,防止出现不合法的操作。例如,withdraw 方法确保余额不会因为取款操作而变成负数。这让账户操作变得安全、可预测。

安全性:由于所有的余额操作都被封装在方法内,可以轻松加入一些额外的规则或验证,比如防止负存款、记录日志、验证交易限额等。这能避免外部代码进行未经检查的非法操作。

// 自定义账户类  
class Account {  
private:  
    int balance;  // 余额  
  
public:  
    // 构造函数,初始化余额  
    Account(int initialBalance) : balance(initialBalance) {}  
  
    // 存款操作  
    void deposit(int amount) {  
        balance += amount;  
    }  
  
    // 取款操作,返回是否成功(防止余额为负)  
    bool withdraw(int amount) {  
        if (balance >= amount) {  
            balance -= amount;  
            return true;  
        }  
        else {  
            return false;  // 余额不足  
        }  
    }  
  
    // 获取当前余额  
    int getBalance() const {  
        return balance;  
    }  
};  

5.1.2主函数设计

创建两个线程 t1 和 t2,分别传入线程 ID(0 和 1),这两个线程会执行 transfer 函数。

使用 join() 等待两个线程完成转账操作。

打印账户 1 和账户 2 的最终余额。

1.int main() {  
2.    // 创建两个线程  
3.    std::thread t1(transfer, 0);  // 线程 1 的 id 为 0  
4.    std::thread t2(transfer, 1);  // 线程 2 的 id 为 1  
5.  
6.    t1.join();  
7.    t2.join();  
8.  
9.    std::cout << "Final Account Balances: " << std::endl;  
10.    std::cout << "Account 1: " << account1.get_balance() << std::endl;  
11.    std::cout << "Account 2: " << account2.get_balance() << std::endl;  
12.  
13.    return 0;  
14.}  

5.1.3未采取同步控制

在已有账户类的基础上,我们创建两个账户

1.// 全局账户对象  
2.BankAccount account1(0);  
3.BankAccount account2(0);  

设计转账函数:转账函数流程图如下:

转账函数具体实现:

1.// 未同步的转账函数  
2.void transfer(int thread_id) {  
3.    int nLoop = 0;  
4.    int nTemp1, nTemp2, nRandom;  
5.  
6.    do {  
7.        nRandom = rand() % 100;  // 随机生成转账金额  
8.        nTemp1 = account1.get_balance();  
9.        nTemp2 = account2.get_balance();  
10.  
11.        // 模拟转账操作  
12.        account1.deposit(nRandom);  
13.        account2.withdraw(nRandom);  
14.  
15.        // 输出每次操作后的账户余额  
16.        std::cout << "Thread " << thread_id << " - Step " << nLoop + 1 << ": "  
17.            << "Account 1: " << account1.get_balance() << ", "  
18.            << "Account 2: " << account2.get_balance() << std::endl;  
19.  
20.        nLoop++;  
21.    } while (account1.get_balance()+ account2.get_balance()==0);  
22.}  
  主函数如下:我们创建了两个线程
1.int main() {  
2.    // 创建两个线程  
3.    std::thread t1(transfer, 1);  
4.    std::thread t2(transfer, 2);  
5.  
6.    t1.join();  
7.    t2.join();  
8.  
9.    std::cout << "Final Account Balances: " << std::endl;  
10.    std::cout << "Account 1: " << account1.get_balance() << std::endl;  
11.    std::cout << "Account 2: " << account2.get_balance() << std::endl;  
12.  
13.    return 0;  
14.}  

5.1.4peterson解决方案

1.// Peterson 算法的全局变量  
2.bool flag[2] = { false, false };  
3.int turn;  

flag 

  • 定义:flag 是一个布尔数组,通常有两个元素 flag[0] 和 flag[1],分别对应两个进程。
  • 作用:用于表示每个进程是否希望进入临界区。具体来说:
    • 如果 flag[i] 为 true,则进程 i 想进入临界区。
    • 如果 flag[i] 为 false,则进程 i 不想进入临界区。

turn

  • 定义:turn 是一个整型变量,通常用来指示哪个进程应该进入临界区。
  • 作用:用于解决两个进程之间的竞争问题,确保在临界区的进程是合适的。具体来说:
    • 当一个进程想进入临界区时,它会将 turn 设置为另一个进程的 ID,以表示它让出进入临界区的权利给另一个进程。
    • 这样可以避免两个进程同时进入临界区的情况,确保了互斥性。

设计peterson算法的同步函数

在 Peterson 算法中,将 turn 设置为另一个线程的 ID 的主要目的是为了实现线程之间的公平性避免饥饿。以下是这一设计决策的几个重要原因:

如果没有优先让另一个线程进入临界区的机制,某个线程可能会一直处于进入临界区的状态,而另一个线程则可能永远无法获得进入的机会。通过设置 turn,确保每个线程都有机会执行临界区代码。

1.// Peterson 算法的同步函数  
2.void enter_critical_section(int thread_id) {  
3.      
4.    //如果 thread_id 是 0,那么 other 就是 1,反之亦然  
5.    int other = 1 - thread_id;     
6.      
7.    //表示当前线程希望进入临界区  
8.    flag[thread_id] = true;  
9.  
10.    //设置 turn 为另一个线程的 ID  
11.    turn = other;  
12.  
13.    //核心,只有当另一个线程不想进入临界区 (flag[other] == false) 或者 turn 变为当前线程时  
14.    //才能退出循环,进入临界区  
15.    while (flag[other] && turn == other);  //flag[other]==0 或者 turn = thread_id时,该线程才能进入临界区
16.}  

设计peterson算法,用于线程离开临界区时的操作

1.void leave_critical_section(int thread_id) {  
2.    flag[thread_id] = false;  
}  

转账操作的函数:通过使用临时变量 nTemp1 和 nTemp2 来暂存账户余额,可以保证在临界区内对账户余额的读取和修改是同步的,并且在修改账户余额前不会发生不一致的情况。

临时变量先获取当前的余额,之后在计算结束后再统一更新账户余额,从而减少意外并发问题。

1.// 使用 Peterson 算法进行同步的转账函数  
2.void transfer(int thread_id) {  
3.    int nLoop = 0;  
4.    int nTemp1, nTemp2, nRandom;  
5.  
6.    do {  
7.        enter_critical_section(thread_id);  // 进入临界区  
8.  
9.        nRandom = rand() % 100;  // 随机生成转账金额  
10.  
11.        //在多线程环境中,两个线程可能会同时读取和修改共享变量(例如 nAccount1 和 nAccount2),导致竞态条件。竞态条件可能导致读取和写入共享数据的顺序出现错误,从而导致错误的结果。  
12.  
13.        //通过使用临时变量 nTemp1 和 nTemp2 来暂存账户余额,  
14.        //可以保证在临界区内对账户余额的读取和修改是同步的,  
15.        //并且在修改账户余额前不会发生不一致的情况。  
16.        //临时变量先获取当前的余额,之后在计算结束后再统一更新账户余额,  
17.        //从而减少意外并发问题。  
18.        nTemp1 = account1.get_balance();  
19.        nTemp2 = account2.get_balance();  
20.  
21.        // 模拟转账操作  
22.        account1.deposit(nRandom);  
23.        account2.withdraw(nRandom);  
24.  
25.        //加上条件  
26.        if (account1.get_balance() + account2.get_balance() != 0) {  
27.            break;  
28.        }  
29.        // 输出每次操作后的账户余额  
30.        std::cout << "Thread " << thread_id << " - Step " << nLoop + 1 << ": "  
31.            << "Account 1: " << nTemp1 << ", "  
32.            << "Account 2: " << nTemp2 << std::endl;  
33.  
34.        leave_critical_section(thread_id);  // 离开临界区  
35.  
36.        nLoop++;  
37.    } while (nLoop < 1000000);  
38.}  

5.1.5windows同步机制

核心函数设计

HANDLE 是 Windows API 中用于表示对象的一个类型,广泛用于各种系统资源的操作,如线程、进程、互斥量、文件、事件等。它是一个指向内部数据结构的指针,提供了一种间接访问这些资源的方式。

1.加锁过程

当一个线程需要访问共享资源时,它会尝试获得互斥量的锁:

请求锁: 线程调用 WaitForSingleObject(hMutex, INFINITE)(在 Windows 中)或 pthread_mutex_lock(&mutex)(在 POSIX 中),请求获得互斥量的控制权。

锁定成功: 如果互斥量当前没有被其他线程锁定,线程将成功获得锁,继续执行临界区的代码。

锁定失败: 如果互斥量已被其他线程锁定,调用线程将被阻塞,直到锁被释放。

只有获得锁的线程可以进入临界区,执行对共享资源的操作(如修改账户余额)。

其他试图获取锁的线程会在此处等待,确保同一时间只有一个线程在修改共享数据,避免数据冲突和不一致。

2.解锁过程:当线程完成了对共享资源的操作后,需要释放互斥量,允许其他线程访问:

线程调用 ReleaseMutex(hMutex)(在 Windows 中)或 pthread_mutex_unlock(&mutex)(在 POSIX 中)释放锁。

1.DWORD WINAPI Transfer(LPVOID lpParameter) {  
2.    int nLoop = 0;  
3.    int nRandom;  
4.    int threadID = *(int*)lpParameter;  // 获取线程 ID  
5.  
6.    while (nLoop < 1000) {  // 假设我们限制循环 1000 次  
7.        nRandom = rand() % 100;  // 限制随机转账金额为 0 到 99 之间  
8.  
9.        // 加锁保护临界区  
10.        WaitForSingleObject(hMutex, INFINITE);  //INFINITE 是一个常量,表示一个无限的时间值,表示线程一直等待,直到某一个条件实现  
11.  
12.        // 检查并进行转账操作,确保不会导致账户 2 余额不足  
13.        if (nAccount2.withdraw(nRandom)) {  
14.            nAccount1.deposit(nRandom);  
15.  
16.            // 使用 cout 打印转账信息  
17.            cout << "线程 " << threadID << ": 转账 " << nRandom  
18.                << " 元,账户 1 余额: " << nAccount1.getBalance()  
19.                << " 元,账户 2 余额: " << nAccount2.getBalance() << " 元" << endl;  
20.        }  
21.  
22.        // 解锁  
23.        ReleaseMutex(hMutex);  
24.  
25.        nLoop++;  
26.        Sleep(10);  // 模拟处理时间  
27.    }  
28.  
29.    return 0;  
30.}  

6.实验结果

6.1未采取同步控制运行

可能会出现以下情况:

  1. 多线程输出混乱,输出语句不完整

  1. 单线程占用资源

没有对 account1 和 account2 的访问进行同步。在多线程环境中,多个线程同时访问和修改共享资源(如账户余额)可能导致数据不一致和竞争条件。此时一个线程可能会在另一个线程完成之前反复执行转账操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值