1.实验内容及要求
针对所谓的银行账户转账同步问题,分析、设计和利用 C 语言编程实现基于 Peterson 算 法的同步解决方案,以及基于 Windows(或 Linux)操作系统同步机制的相应解决方案,并就 自编同步机制与操作系统自身同步机制的效率进行比较和分析。
同步机制及应用编程实现与比较实验功能设计要求:
(1)银行账户转账同步问题的抽象及未采取同步控制情况下的编程实现;
(2)基于 Peterson 算法的银行账户转账同步问题解决方案;
(3)基于 Windows(或 Linux)操作系统同步机制的银行账户转账同步问题解决方案;
(4)Peterson 算法同步机制和 Windows(或 Linux)操作系统同步机制的效率比较。
2.开发/运行/测试环境:
操作系统:Windows10
平台工具集:Visual Studio 2022 (v143)
C++语言标准:ISO C++17 标准 (/std:c++17)
C语言标准:默认(旧 MSVC)
3.源代码
(1)无同步互斥线程
1 //无同步互斥线程 2 #include<stdio.h> 3 #include<time.h> 4 #include<windows.h> 5 #include<iostream> 6 using namespace std; 7 8 //全局变量 9 int nAccount1 = 0, nAccount2 = 0;//转入账户的原金额,转出账户的原金额 10 int nLoop1 = 0;//2向1转账次数 11 int nLoop2 = 0;//1向2转账次数 12 13 DWORD WINAPI Tran_2to1(HANDLE Thread)//2向1转账 14 { 15 int nTemp1, nTemp2, nRandom; 16 17 do 18 { 19 nRandom = rand()%1000; 20 printf("转账金额:%d 2 to 1\n", nRandom); 21 nTemp1 = nAccount1; 22 nTemp2 = nAccount2; 23 nAccount1 = nTemp1 + nRandom; 24 printf("1账户余额:%d\n", nAccount1); 25 nAccount2 = nTemp2 - nRandom; 26 printf("2账户余额:%d\n", nAccount2); 27 nLoop1++; 28 printf("2向1转账次数:第%d次\n\n", nLoop1); 29 30 } while ((nAccount1 + nAccount2) == 0); 31 return 0; 32 } 33 34 DWORD WINAPI Tran_1to2(HANDLE Thread)//1向2转账 35 { 36 int nTemp1, nTemp2, nRandom; 37 do 38 { 39 nRandom = rand()%1000; 40 printf("转账金额:%d 1 to 2\n", nRandom); 41 nTemp1 = nAccount1; 42 nTemp2 = nAccount2; 43 nAccount1 = nTemp1 - nRandom; 44 printf("1账户余额:%d\n", nAccount1); 45 nAccount2 = nTemp2 + nRandom; 46 printf("2账户余额:%d\n", nAccount2); 47 nLoop2++; 48 printf("1向2转账次数:第%d次\n\n", nLoop2); 49 50 } while ((nAccount1 + nAccount2) == 0); 51 return 0; 52 } 53 54 int main() 55 { 56 57 HANDLE Thread[2]; 58 DWORD starttime, endtime; 59 srand(unsigned(time(NULL))); 60 61 starttime = GetTickCount(); 62 63 Thread[0] = CreateThread(NULL, 0, Tran_2to1, NULL, 0, NULL);//创建线程函数 64 Thread[1] = CreateThread(NULL, 0, Tran_1to2, NULL, 0, NULL); 65 WaitForMultipleObjects(2, Thread, TRUE, INFINITE);//等待 66 printf("2向1转账总次数:%d次\n", nLoop1); 67 printf("2向1转账总次数:%d次\n", nLoop2); 68 69 endtime = GetTickCount(); 70 printf("运行时间为:%ld ms\n", endtime - starttime); 71 72 CloseHandle(Thread[0]);//结束线程 73 CloseHandle(Thread[1]); 74 75 return 0; 76 }
(2) Peterson算法
Peterson算法是基于双线程互斥访问的LockOne与LockTwo算法而来。LockOne算法使用一个flag布尔数组,LockTwo使用一个turn的整型量,都实现了互斥,但是都存在死锁的可能。Peterson算法把这两种算法结合起来,完美地用软件实现了双线程互斥问题。
算法使用两个控制变量flag与turn. 其中flag[n]的值为真,表示ID号为n的进程希望进入该临界区.,变量turn保存有权访问共享资源的进程的ID号。
当某一进程的flag被置为true后:
若另一进程直接执行到turn语句,会不得不在while中等待;
若另一进程执行到flag语句后又轮到原进程,则原进程在while中等待,先执行另一进程。
//Peterson算法 #include<stdio.h> #include<time.h> #include<windows.h> #include<iostream> using namespace std; //全局变量 int nAccount1 = 0, nAccount2 = 0;//转入账户的原金额,转出账户的原金额 int nLoop1 = 0;//2向1转账次数 int nLoop2 = 0;//1向2转账次数 bool flag1 = false; bool flag2 = false; int turn = 1; DWORD WINAPI Tran_2to1(HANDLE Thread)//2向1转账 { int nTemp1, nTemp2, nRandom; do { nRandom = rand() % 1000; printf("转账金额:%d 2 to 1\n", nRandom); flag1 = true; turn = 2; while (flag2 && turn == 2); /*******************临界区**********************/ nTemp1 = nAccount1; nTemp2 = nAccount2; nAccount1 = nTemp1 + nRandom; printf("1账户余额:%d\n", nAccount1); nAccount2 = nTemp2 - nRandom; printf("2账户余额:%d\n", nAccount2); /*******************临界区**********************/ flag1 = false; nLoop1++; printf("2向1转账次数:第%d次\n\n", nLoop1); } while (nLoop1 < 1000000); return 0; } DWORD WINAPI Tran_1to2(HANDLE Thread)//1向2转账 { int nTemp1, nTemp2, nRandom; do { nRandom = rand() % 1000; printf("转账金额:%d 1 to 2\n", nRandom); flag2 = true; turn = 1; while (flag1 && turn == 1); /*******************临界区**********************/ nTemp1 = nAccount1; nTemp2 = nAccount2; nAccount1 = nTemp1 - nRandom; printf("1账户余额:%d\n", nAccount1); nAccount2 = nTemp2 + nRandom; printf("2账户余额:%d\n", nAccount2); /*******************临界区**********************/ flag2 = false; nLoop2++; printf("1向2转账次数:第%d次\n\n", nLoop2); } while (nLoop2 < 1000000); return 0; } int main() { HANDLE Thread[2]; DWORD starttime, endtime; srand(unsigned(time(NULL))); starttime = GetTickCount(); Thread[0] = CreateThread(NULL, 0, Tran_2to1, NULL, 0, NULL);//创建线程函数 Thread[1] = CreateThread(NULL, 0, Tran_1to2, NULL, 0, NULL); WaitForMultipleObjects(2, Thread, TRUE, INFINITE);//等待 printf("2向1转账总次数:%d次\n", nLoop1); printf("2向1转账总次数:%d次\n", nLoop2); endtime = GetTickCount(); printf("运行时间为:%ld ms\n", endtime - starttime); CloseHandle(Thread[0]);//结束线程 CloseHandle(Thread[1]); return 0; }
(3)windows同步互斥线程
1 //windows同步互斥线程 2 #include<stdio.h> 3 #include<time.h> 4 #include<windows.h> 5 #include<iostream> 6 using namespace std; 7 8 //全局变量 9 int nAccount1 = 0, nAccount2 = 0;//转入账户的原金额,转出账户的原金额 10 int nLoop1 = 0;//2向1转账次数 11 int nLoop2 = 0;//1向2转账次数 12 HANDLE mutex;//声明一个内核对象 13 14 DWORD WINAPI Tran_2to1(HANDLE Thread)//2向1转账 15 { 16 int nTemp1, nTemp2, nRandom; 17 18 do 19 { 20 WaitForSingleObject(mutex, INFINITE); //等待互斥量触发 21 22 nRandom = rand() % 1000; 23 printf("转账金额:%d 2 to 1\n", nRandom); 24 nTemp1 = nAccount1; 25 nTemp2 = nAccount2; 26 nAccount1 = nTemp1 + nRandom; 27 printf("1账户余额:%d\n", nAccount1); 28 nAccount2 = nTemp2 - nRandom; 29 printf("2账户余额:%d\n", nAccount2); 30 if (nAccount1 + nAccount2 != 0) 31 break; 32 nLoop1++; 33 printf("2向1转账次数:第%d次\n\n", nLoop1); 34 35 ReleaseMutex(mutex);//释放互斥量 36 37 } while (nLoop1 < 100000);; 38 return 0; 39 } 40 41 DWORD WINAPI Tran_1to2(HANDLE Thread)//1向2转账 42 { 43 int nTemp1, nTemp2, nRandom; 44 do 45 { 46 WaitForSingleObject(mutex, INFINITE); //等待互斥量触发 47 48 nRandom = rand() % 1000; 49 printf("转账金额:%d 1 to 2\n", nRandom); 50 nTemp1 = nAccount1; 51 nTemp2 = nAccount2; 52 nAccount1 = nTemp1 - nRandom; 53 printf("1账户余额:%d\n", nAccount1); 54 nAccount2 = nTemp2 + nRandom; 55 printf("2账户余额:%d\n", nAccount2); 56 if (nAccount1 + nAccount2 != 0) 57 break; 58 nLoop2++; 59 printf("1向2转账次数:第%d次\n\n", nLoop2); 60 61 ReleaseMutex(mutex);//释放互斥量 62 63 } while (nLoop2 < 100000); 64 return 0; 65 } 66 67 int main() 68 { 69 70 HANDLE Thread[2]; 71 DWORD starttime, endtime; 72 srand(unsigned(time(NULL))); 73 74 mutex = CreateMutex(NULL, FALSE, NULL);// 创建互斥量,初始化为触发状态 75 76 starttime = GetTickCount(); 77 78 Thread[0] = CreateThread(NULL, 0, Tran_2to1, NULL, 0, NULL);//创建线程函数 79 Thread[1] = CreateThread(NULL, 0, Tran_1to2, NULL, 0, NULL); 80 WaitForMultipleObjects(2, Thread, TRUE, INFINITE);//一直等待,直到所有子线程全部返回 81 printf("2向1转账总次数:%d次\n", nLoop1); 82 printf("2向1转账总次数:%d次\n", nLoop2); 83 84 endtime = GetTickCount(); 85 printf("运行时间为:%ld ms\n", endtime - starttime); 86 87 CloseHandle(Thread[0]);//结束线程 88 CloseHandle(Thread[1]); 89 90 return 0; 91 }
4.疑难解惑及经验教训
如果采用#include<thread>及其方法,会导致无法nAccount1 + nAccount2 == 0恒成立,从而使程序无法跳出循环。尚不清楚为何会出现这种情况。
最初我认为是线程构建失败,从而导致两个线程函数退化为普通函数,导致仅有一个函数被执行且在此函数中不断循环,从而解释为何nAccount1 + nAccount2 == 0恒成立。但经过测试,发现两个函数都得到了执行,从而排除了此种情况。
1 /*//无同步互斥线程 2 #include<stdio.h> 3 #include<time.h> 4 #include<thread> 5 #include<iostream> 6 using namespace std; 7 8 void Tran_2to1(int nAccount1, int nAccount2);//2向1转账 9 void Tran_1to2(int nAccount1, int nAccount2);//1向2转账 10 11 //全局变量 12 int nAccount1 = 0, nAccount2 = 0;//转入账户的原金额,转出账户的原金额 13 14 int main() 15 { 16 17 srand(unsigned(time(NULL))); 18 19 thread AC1(Tran_2to1, nAccount1, nAccount2); 20 thread AC2(Tran_1to2, nAccount1, nAccount2); 21 22 AC1.join(); 23 AC2.join(); 24 return 0; 25 } 26 27 void Tran_2to1(int nAccount1, int nAccount2)//2向1转账 28 { 29 int nTemp1, nTemp2, nRandom; 30 int nLoop = 0;//2向1转账次数 31 32 do 33 { 34 nRandom = rand()%1000; 35 printf("转账金额:%d 2 to 1\n", nRandom); 36 nTemp1 = nAccount1; 37 nTemp2 = nAccount2; 38 nAccount1 = nTemp1 + nRandom; 39 printf("1账户余额:%d\n", nAccount1); 40 nAccount2 = nTemp2 - nRandom; 41 printf("2账户余额:%d\n\n", nAccount2); 42 nLoop++; 43 printf("2向1转账次数:第%d次\n", nLoop); 44 45 } while ((nAccount1 + nAccount2) == 0); 46 return; 47 } 48 49 void Tran_1to2(int nAccount1, int nAccount2)//1向2转账 50 { 51 int nTemp1, nTemp2, nRandom; 52 int nLoop = 0;//1向2转账次数 53 54 do 55 { 56 nRandom = rand()%1000; 57 printf("转账金额:%d 1 to 2\n", nRandom); 58 nTemp1 = nAccount1; 59 nTemp2 = nAccount2; 60 nAccount1 = nTemp1 - nRandom; 61 printf("1账户余额:%d\n", nAccount1); 62 nAccount2 = nTemp2 + nRandom; 63 printf("2账户余额:%d\n\n", nAccount2); 64 nLoop++; 65 printf("1向2转账次数:第%d次\n", nLoop); 66 67 } while ((nAccount1 + nAccount2) == 0); 68 return; 69 }*/
5.结论与体会
分别使用Peterson算法和Windows的同步互斥机制对两个进程同样执行100000次,可以看到Windows的同步互斥机制所用时间更短,性能更佳。
体会:实验要提前做,不要等到deadline附近。虽然日期的迫近会使效率变高,但也会带来不必要的焦虑。此外,时间的不充裕也会对实验质量带来影响。
建议:化整为零,步步扎营,稳扎稳打。
操作系统的知识博大精深,关联了多门专业课的知识,学好操作系统极其有必要。
标签:转账,操作系统,int,编程,nAccount1,nAccount2,nRandom,printf From: https://www.cnblogs.com/jeffrey188/p/16797298.html