当前位置: 首页 > news >正文

燕山大学多核程序设计实验(25最新版)

实验一 Windows多线程编程基础练习

一. 实验目的与要求

学习多核多线程的基本编程方式

二.实验环境及软件

OS:Windows 11

软件工具:Visual Studio

三.   实验内容

练习线程的创建,同步函数的使用

四.  实验代码和结果分析

  1. Hello World程序   

(1)实验代码 

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

#include <windows.h>

#include <iostream>

using namespace std;

 

// 全局退出标志

volatile bool g_running = true;

 

DWORD WINAPI FunOne(LPVOID param) {

    while (g_running) {  // 检查退出标志

        Sleep(1000);

        cout << "hello! ";

    }

    return 0;

}

 

DWORD WINAPI FunTwo(LPVOID param) {

    while (g_running) {  // 检查退出标志

        Sleep(1000);

        cout << "world! ";

    }

    return 0;

}

 

int main() {

    HANDLE hand1 = CreateThread(NULL, 0, FunOne, NULL, CREATE_SUSPENDED, NULL);

    HANDLE hand2 = CreateThread(NULL, 0, FunTwo, NULL, CREATE_SUSPENDED, NULL);

     

    cout << "Thread control commands:\n";

    cout << "  1: Start threads\n";

    cout << "  2: Pause threads\n";

    cout << "  0: Exit program\n";

 

    while (true) {

        int input;

        cin >> input;

         

        if (input == 1) {

            ResumeThread(hand1);

            ResumeThread(hand2);

            cout << "Threads started\n";

        } else if (input == 2) {

            SuspendThread(hand1);

            SuspendThread(hand2);

            cout << "Threads paused\n";

        } else if (input == 0) {

            g_running = false;  // 设置退出标志

            break;

        }

    }

 

    // 确保线程恢复执行以便检测退出标志

    ResumeThread(hand1);

    ResumeThread(hand2);

     

    // 等待线程安全退出

    WaitForSingleObject(hand1, INFINITE);

    WaitForSingleObject(hand2, INFINITE);

     

    CloseHandle(hand1);

    CloseHandle(hand2);

     

    cout << "\nProgram exited safely\n";

    return 0;

}

(2)实验结果及分析

图1-1 “Hello World”

给的代码通过输入值控制线程挂起/恢复,但存在两个关键问题:一是线程函数采用无限循环却无退出机制,导致TerminateThread无法被执行(主线程阻塞在while(true));二是强制终止线程存在资源泄漏风险。修改后版本引入全局标志g_running实现安全退出:当输入0时,主线程设置退出标志→恢复挂起线程→等待线程自然结束。实验结果显示,线程能按指令启停(输入1启动/输入2暂停),且输入0后程序在1秒内安全退出(等待Sleep结束)

2.线程的创建执行

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

#include <windows.h>

#include <iostream>

using namespace std;

 

// 全局退出标志

volatile bool g_running = true;

 

DWORD WINAPI ThreadFunc1(LPVOID param) {

    while (g_running) {

        Sleep(1000);

        cout << "Thread1: Hello" << endl;

    }

    return 0;

}

 

DWORD WINAPI ThreadFunc2(LPVOID param) {

    while (g_running) {

        Sleep(1500);

        cout << "Thread2: World" << endl;

    }

    return 0;

}

 

int main() {

    // 创建线程(初始挂起状态)

    HANDLE hand1 = CreateThread(NULL, 0, ThreadFunc1, NULL, CREATE_SUSPENDED, NULL);

    HANDLE hand2 = CreateThread(NULL, 0, ThreadFunc2, NULL, CREATE_SUSPENDED, NULL);

 

    cout << "=== 线程控制程序 ===" << endl;

    cout << "1: 启动线程\n2: 暂停线程\n3: 恢复线程\n0: 退出程序\n";

 

    // 初始启动线程

    ResumeThread(hand1);

    ResumeThread(hand2);

    cout << "线程已启动!" << endl;

 

    while (true) {

        int input;

        cout << "> ";

        cin >> input;

 

        if (input == 1) {

            ResumeThread(hand1);

            ResumeThread(hand2);

            cout << "线程已启动!" << endl;

        }

        else if (input == 2) {

            SuspendThread(hand1);

            SuspendThread(hand2);

            cout << "线程已暂停!" << endl;

        }

        else if (input == 3) {

            ResumeThread(hand1);

            ResumeThread(hand2);

            cout << "线程已恢复!" << endl;

        }

        else if (input == 0) {

            g_running = false;  // 设置退出标志

            break;

        }

    }

 

    // 确保线程恢复以便检测退出标志

    ResumeThread(hand1);

    ResumeThread(hand2);

 

    // 等待线程结束

    WaitForSingleObject(hand1, INFINITE);

    WaitForSingleObject(hand2, INFINITE);

 

    CloseHandle(hand1);

    CloseHandle(hand2);

 

    cout << "程序安全退出!" << endl;

    return 0;

}

(2)实验结果及分析

      

图1-2 “线程创建与执行”

在给的示例代码中,缺乏控制机制并且有强制终止的安全隐患。进行修改后,通过CreateThread的CREATE_SUSPENDED参数实现可控启动,引入全局标志g_running确保安全退出实验结果验证了线程的并发特性,即(Thread1每秒输出"Hello"与Thread2每1.5秒输出"World"的异步执行),同时状态控制指令(启动/暂停/恢复)响应即时,退出流程完整(恢复线程→检测标志→资源释放)。

3.全局变量实现线程的同步

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

#include <windows.h>

#include <iostream>

using namespace std;

 

// 添加volatile确保内存可见性

volatile bool globalvar = false;

 

DWORD WINAPI ThreadFunc(LPVOID pParam) {

    cout << "子线程启动:设置全局变量" << endl;

    Sleep(200); // 模拟耗时操作

    globalvar = true;

    cout << "子线程完成:globalvar=true" << endl;

    return 0;

}

 

int main() {

    HANDLE hthread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);

    if (!hthread) {

        cerr << "线程创建失败!" << endl;

        return 1;

    }

     

    cout << "主线程等待全局变量变化..." << endl;

    while (!globalvar) {

        Sleep(100); // 降低CPU占用

        cout << "等待中...";

    }

     

    cout << "\n全局变量已更新,主线程继续执行" << endl;

    WaitForSingleObject(hthread, INFINITE); // 等待子线程结束

    CloseHandle(hthread);

    return 0;

}

(2)实验结果和分析

   

图1-3 “全局变量实现线程同步”

原始代码通过全局变量globalvar实现线程同步,但有两个问题,一个是主线程的忙等待循环while(!globalvar)会持续占用CPU资源。还有一个是缺少内存屏障机制可能导致可见性问题。我们进行修改后添加volatile关键字确保内存可见性,在循环中引入Sleep(100)降低CPU占用,并增加WaitForSingleObject保证子线程安全退出。实验结果清晰展示了同步过程:主线程持续检测全局变量状态,子线程完成200ms任务后更新全局变量,触发主线程继续执行。

4.  互斥量

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

#include <windows.h>

#include <iostream>

using namespace std;

 

#define THREAD_INSTANCE_NUMBER 3

LONG g_lCounter = 0// 共享计数器

 

DWORD WINAPI ThreadProc(LPVOID pData) {

    int threadNum = *(int*)pData;

    // 使用宽字符版本或TEXT宏

    HANDLE hMutex = OpenMutexW(MUTEX_ALL_ACCESS, FALSE, L"Mutex.Test");

     

    if (!hMutex) {

        cerr << "线程" << threadNum << ": 打开互斥量失败!" << endl;

        return 1;

    }

 

    // 请求互斥量所有权

    WaitForSingleObject(hMutex, INFINITE);

    cout << "== 线程" << threadNum << "进入临界区 ==" << endl;

     

    // 临界区操作

    cout << "  当前计数器值: " << g_lCounter << endl;

    g_lCounter += threadNum;

    cout << "  更新后计数器值: " << g_lCounter << endl;

    Sleep(500); // 模拟处理时间

     

    cout << "== 线程" << threadNum << "离开临界区 ==\n" << endl;

    ReleaseMutex(hMutex);

    CloseHandle(hMutex);

    return 0;

}

 

int main() {

    HANDLE hThreads[THREAD_INSTANCE_NUMBER];

    int threadIDs[THREAD_INSTANCE_NUMBER] = {1, 2, 3};

     

    // 创建互斥量(使用宽字符版本)

    HANDLE hMutex = CreateMutexW(NULL, FALSE, L"Mutex.Test");

    if (!hMutex) {

        cerr << "创建互斥量失败!" << endl;

        return 1;

    }

 

    // 创建线程

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++) {

        hThreads[i] = CreateThread(NULL, 0, ThreadProc, &threadIDs[i], 0, NULL);

        if (!hThreads[i]) {

            cerr << "创建线程" << threadIDs[i] << "失败" << endl;

        }

    }

 

    // 等待所有线程完成

    WaitForMultipleObjects(THREAD_INSTANCE_NUMBER, hThreads, TRUE, INFINITE);

     

    // 清理资源

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++)

        CloseHandle(hThreads[i]);

     

    CloseHandle(hMutex);

     

    cout << "最终计数器值: " << g_lCounter << endl;

    cout << "程序正常退出" << endl;

    return 0;

}

(2)实验结果与分析

图1-4 “互斥量”

线程通过WaitForSingleObject获取互斥锁,在临界区内顺序操作共享计数器g_lCounter,然后通过ReleaseMutex释放锁。实验结果能够验证互斥量的有效性:三个线程按创建顺序依次进入临界区(实际顺序可能因系统调度而异),计数器累加结果6(1+2+3)能够证明共享资源操作具有原子性。

5. 事件

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

#include <windows.h>

#include <iostream>

using namespace std;

 

// 使用显式宽字符API解决编码问题

HANDLE hEventRead, hEventFinish;

 

DWORD WINAPI ReadThread(LPVOID param) {

    WaitForSingleObject(hEventRead, INFINITE);  // 等待读取事件

    cout << "读取线程:正在处理数据..." << endl;

    Sleep(800);  // 模拟读取耗时

    cout << "读取线程:数据读取完成!" << endl;

 

    SetEvent(hEventFinish);  // 触发完成事件

    return 0;

}

 

DWORD WINAPI WriteThread(LPVOID param) {

    cout << "写入线程:开始生成数据..." << endl;

    Sleep(1200);  // 模拟写入耗时

    cout << "写入线程:数据准备就绪!" << endl;

 

    SetEvent(hEventRead);  // 触发读取事件

    return 0;

}

 

int main() {

    // 创建事件对象(手动重置+初始无信号)

    hEventRead = CreateEventW(NULL, TRUE, FALSE, L"ReadEvent");

    hEventFinish = CreateEventW(NULL, TRUE, FALSE, L"FinishEvent");

 

    if (!hEventRead || !hEventFinish) {

        cerr << "事件创建失败!" << endl;

        return 1;

    }

 

    // 创建线程

    HANDLE hThreads[2];

    hThreads[0] = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);

    hThreads[1] = CreateThread(NULL, 0, WriteThread, NULL, 0, NULL);

 

    if (!hThreads[0] || !hThreads[1]) {

        cerr << "线程创建失败!" << endl;

        return 1;

    }

 

    // 等待完成事件

    WaitForSingleObject(hEventFinish, INFINITE);

    cout << "\n主线程:收到完成信号,程序结束" << endl;

 

    // 清理资源

    for (auto h : hThreads) CloseHandle(h);

    CloseHandle(hEventRead);

    CloseHandle(hEventFinish);

 

    return 0;

}

2实验结果与分析

通过CreateThread确保线程生命周期可控,使用CreateEventW解决编码兼容性问题,并添加资源释放逻辑。最后我们展示了事件驱动的线程协作:写入线程首先启动,完成后触发读取事件;读取线程处理数据后触发完成事件;主线程收到完成信号后退出。

图1-5 “事件”

6. 信号量

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

#include <windows.h>

#include <iostream>

using namespace std;

 

#define THREAD_INSTANCE_NUMBER 5  // 增加线程数以便更好展示信号量

#define MAX_CONCURRENT_THREADS 2  // 最大并发线程数

 

// 线程函数

DWORD WINAPI ThreadProc(LPVOID pData) {

    int threadNum = *(int*)pData;

    HANDLE hSemaphore = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS, FALSE, L"Semaphore.Test");

     

    if (!hSemaphore) {

        cerr << "线程" << threadNum << ": 打开信号量失败!" << endl;

        return 1;

    }

 

    // 请求信号量许可

    WaitForSingleObject(hSemaphore, INFINITE);

    cout << "== 线程" << threadNum << "获得信号量许可 ==" << endl;

    cout << "  正在执行任务..." << endl;

    Sleep(1000 * (threadNum % 2 + 1)); // 模拟不同长度的任务

     

    cout << "== 线程" << threadNum << "完成任务,释放许可 ==\n" << endl;

    ReleaseSemaphore(hSemaphore, 1, NULL);

    CloseHandle(hSemaphore);

    return 0;

}

 

int main() {

    HANDLE hThreads[THREAD_INSTANCE_NUMBER];

    int threadIDs[THREAD_INSTANCE_NUMBER];

     

    // 初始化线程ID

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++) {

        threadIDs[i] = i + 1;

    }

     

    // 创建信号量(宽字符解决编码问题)

    HANDLE hSemaphore = CreateSemaphoreW(NULL, MAX_CONCURRENT_THREADS, MAX_CONCURRENT_THREADS, L"Semaphore.Test");

    if (!hSemaphore) {

        cerr << "创建信号量失败!" << endl;

        return 1;

    }

 

    // 创建线程

    cout << "创建 " << THREAD_INSTANCE_NUMBER << " 个线程..." << endl;

    cout << "最大并发数: " << MAX_CONCURRENT_THREADS << "\n" << endl;

     

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++) {

        hThreads[i] = CreateThread(NULL, 0, ThreadProc, &threadIDs[i], 0, NULL);

        if (!hThreads[i]) {

            cerr << "创建线程" << threadIDs[i] << "失败" << endl;

        }

        Sleep(200); // 延迟创建以更好展示并发控制

    }

 

    // 等待所有线程完成

    WaitForMultipleObjects(THREAD_INSTANCE_NUMBER, hThreads, TRUE, INFINITE);

     

    // 清理资源

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++)

        CloseHandle(hThreads[i]);

     

    CloseHandle(hSemaphore);

     

    cout << "所有线程执行完毕" << endl;

    return 0;

}

(2)实验结果与分析

原始代码创建了信号量但未实际用于同步控制,缺陷在于线程函数中缺少WaitForSingleObject请求信号量。在进行修改后,我们实现了信号量同步机制,创建信号量时设置初始计数和最大计数为2,使每个线程执行前必须通过WaitForSingleObject获取信号量许可,在任务完成后用ReleaseSemaphore释放许可。

图1-6 “信号量”实例

7. 临界区

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

#include <windows.h>

#include <iostream>

using namespace std;

 

// 共享资源

int accountBalance = 1000// 初始账户余额

CRITICAL_SECTION g_cs;      // 临界区对象

 

DWORD WINAPI WithdrawThread1(LPVOID param) {

    // 进入临界区

    EnterCriticalSection(&g_cs);

 

    cout << "线程1:尝试取款800元..." << endl;

    if (accountBalance >= 800) {

        accountBalance -= 800;

        cout << "  成功取款800元,余额: " << accountBalance << endl;

    }

    else {

        cout << "  余额不足!" << endl;

    }

 

    // 离开临界区

    LeaveCriticalSection(&g_cs);

    return 0;

}

 

DWORD WINAPI WithdrawThread2(LPVOID param) {

    // 进入临界区

    EnterCriticalSection(&g_cs);

 

    cout << "线程2:尝试取款500元..." << endl;

    if (accountBalance >= 500) {

        accountBalance -= 500;

        cout << "  成功取款500元,余额: " << accountBalance << endl;

    }

    else {

        cout << "  余额不足!" << endl;

    }

 

    // 离开临界区

    LeaveCriticalSection(&g_cs);

    return 0;

}

 

int main() {

    // 初始化临界区

    InitializeCriticalSection(&g_cs);

 

    // 创建线程

    HANDLE hThreads[2];

    hThreads[0] = CreateThread(NULL, 0, WithdrawThread1, NULL, 0, NULL);

    hThreads[1] = CreateThread(NULL, 0, WithdrawThread2, NULL, 0, NULL);

 

    if (!hThreads[0] || !hThreads[1]) {

        cerr << "线程创建失败!" << endl;

        return 1;

    }

 

    // 等待线程结束

    WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);

 

    // 清理资源

    for (auto h : hThreads) CloseHandle(h);

    DeleteCriticalSection(&g_cs);

 

    cout << "\n最终账户余额: " << accountBalance << endl;

    return 0;

}

2实验结果与分析

图1-7 “临界区”实例

使用CreateThread创建线程,然后直接通过临界区保护共享资源accountBalance。使用WaitForMultipleObjects等待线程结束。临界区的核心作用:任一时刻只有一个线程能访问共享余额,确保余额更新操作的原子性。最终余额取决于线程执行顺序(可能200或500),但不会出现负数等异常状态。

8. 综合实例

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

#include <windows.h>

#include <iostream>

using namespace std;

 

// 共享资源

int tickets = 10// 总票数减少以便观察

CRITICAL_SECTION g_cs;  // 临界区对象

 

DWORD WINAPI TicketSeller(LPVOID lpParameter) {

    int threadID = (int)lpParameter;

    int soldCount = 0// 记录本线程售票数量

 

    while (true) {

        // 进入临界区

        EnterCriticalSection(&g_cs);

 

        // 检查票数

        if (tickets <= 0) {

            LeaveCriticalSection(&g_cs);

            break;

        }

 

        // 售票操作

        cout << "售票员" << threadID << "售出票号: " << tickets;

        tickets--;

        soldCount++;

 

        // 模拟售票耗时

        cout << " (剩余票数: " << tickets << ")" << endl;

 

        // 离开临界区

        LeaveCriticalSection(&g_cs);

 

        Sleep(rand() % 200);  // 随机等待时间模拟实际售票过程

    }

 

    cout << "== 售票员" << threadID << "结束工作,共售出" << soldCount << "张票 ==" << endl;

    return 0;

}

 

int main() {

    const int SELLER_COUNT = 3// 售票员数量

 

    // 初始化临界区

    InitializeCriticalSection(&g_cs);

 

    // 创建线程(售票员)

    HANDLE hSellers[SELLER_COUNT];

    for (int i = 0; i < SELLER_COUNT; i++) {

        hSellers[i] = CreateThread(NULL, 0, TicketSeller, (LPVOID)(i + 1), 0, NULL);

        if (!hSellers[i]) {

            cerr << "创建售票员" << i + 1 << "失败!" << endl;

        }

    }

 

    // 等待所有售票员结束工作

    WaitForMultipleObjects(SELLER_COUNT, hSellers, TRUE, INFINITE);

 

    // 清理资源

    for (int i = 0; i < SELLER_COUNT; i++)

        CloseHandle(hSellers[i]);

 

    DeleteCriticalSection(&g_cs);

 

    cout << "\n所有票已售罄,系统关闭" << endl;

    return 0;

}

(2)实验结果与分析

图1-8 综合实例

我们使用统一的TicketSeller线程函数,让每个售票员记录个人售票数量并添加随机等待时间模拟真实场景,使用WaitForMultipleObjects等待所有线程结束,最终展示多线程售票过程,即三个售票员交替售票(因随机等待时间,售票数量不均衡),临界区确保每张票仅售出一次,最终票数归零后系统安全退出。
              

                  

                           实验 Windows多线程改写练习

一.  实验目的与要求

练习编写多线程程序 

二.实验环境及软件

OS:Windows 11 家庭版

软件工具:Visual Studio

三.  实验内容

改写例1,4,5。

四.  实验代码和结果分析 

  1. 改写课上的例1,实现顺序输出This is Threadfunc1, This is Threadfunc2。

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

#include <windows.h>

#include <iostream>

using namespace std;

 

// 添加两个事件用于线程同步

HANDLE hEvent1, hEvent2;

 

DWORD WINAPI ThreadFunc1(LPVOID param) {

    while (true) {

        // 等待事件1触发

        WaitForSingleObject(hEvent1, INFINITE);

 

        cout << "This is ThreadFunc1" << endl;

        Sleep(1000);

 

        // 触发事件2,允许ThreadFunc2执行

        SetEvent(hEvent2);

    }

    return 0;

}

 

DWORD WINAPI ThreadFunc2(LPVOID param) {

    while (true) {

        // 等待事件2触发

        WaitForSingleObject(hEvent2, INFINITE);

 

        cout << "This is ThreadFunc2" << endl;

        Sleep(1000);

 

        // 触发事件1,允许ThreadFunc1执行

        SetEvent(hEvent1);

    }

    return 0;

}

 

int main() {

    // 创建事件(自动重置)

    hEvent1 = CreateEvent(NULL, FALSE, TRUE, NULL);  // 初始有信号,ThreadFunc1先执行

    hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL); // 初始无信号

 

    // 创建线程

    HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);

    HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);

 

    // 主线程等待10秒后退出

    Sleep(10000);

 

    // 终止线程(实际应用应使用更优雅的退出方式)

    TerminateThread(hThread1, 0);

    TerminateThread(hThread2, 0);

 

    // 清理资源

    CloseHandle(hThread1);

    CloseHandle(hThread2);

    CloseHandle(hEvent1);

    CloseHandle(hEvent2);

 

    cout << "程序结束" << endl;

    return 0;

}

(2)实验结果与分析

图2-1 例1改写

原来的代码中两个线程并发执行会导致输出顺序随机,修改后通过事件对象实现严格顺序控制,例如使用hEvent1(初始有信号)触发ThreadFunc1执行。 ThreadFunc1完成后通过SetEvent(hEvent2)激活ThreadFunc2。ThreadFunc2完成后通过SetEvent(hEvent1)重新激活ThreadFunc1,形成闭环同步链。

2.  改写课上的例4,用事件代替互斥量实现线程间的同步。

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

#include <windows.h>

#include <iostream>

using namespace std;

 

#define THREAD_INSTANCE_NUMBER 3

 

// 使用事件对象替代互斥量

HANDLE g_hEvent;

LONG g_lCounter = 0// 共享资源

 

DWORD WINAPI ThreadProc(LPVOID pData) {

    int threadNum = *(int*)pData;

 

    cout << "线程" << threadNum << ": 等待进入临界区..." << endl;

 

    // 等待事件信号(替代互斥量等待)

    WaitForSingleObject(g_hEvent, INFINITE);

 

    // 临界区开始

    cout << "线程" << threadNum << ": 进入临界区" << endl;

    cout << "  原始计数器值: " << g_lCounter << endl;

 

    // 操作共享资源

    g_lCounter += threadNum;

    Sleep(500); // 模拟处理时间

 

    cout << "  更新后计数器值: " << g_lCounter << endl;

 

    // 临界区结束

    cout << "线程" << threadNum << ": 离开临界区\n" << endl;

 

    // 触发事件允许下一个线程进入

    SetEvent(g_hEvent);

    return 0;

}

 

int main() {

    HANDLE hThreads[THREAD_INSTANCE_NUMBER];

    int threadIDs[THREAD_INSTANCE_NUMBER] = { 1, 2, 3 };

 

    // 创建手动重置事件(初始有信号)

    g_hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);

    if (!g_hEvent) {

        cerr << "创建事件失败!" << endl;

        return 1;

    }

 

    // 创建线程

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++) {

        threadIDs[i] = i + 1;

        hThreads[i] = CreateThread(NULL, 0, ThreadProc, &threadIDs[i], 0, NULL);

        if (!hThreads[i]) {

            cerr << "创建线程" << threadIDs[i] << "失败" << endl;

        }

        Sleep(100); // 延迟创建以观察等待过程

    }

 

    // 等待所有线程完成

    WaitForMultipleObjects(THREAD_INSTANCE_NUMBER, hThreads, TRUE, INFINITE);

 

    // 清理资源

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++)

        CloseHandle(hThreads[i]);

 

    CloseHandle(g_hEvent);

 

    cout << "最终计数器值: " << g_lCounter << endl;

    cout << "程序正常退出" << endl;

    return 0;

}

(2)实验分析与结果

图2-2 例4 改写

原来的代码使用互斥量保护共享资源,现改用事件对象实现相同功能,创建手动重置事件g_hEvent(初始有信号),线程通过WaitForSingleObject等待事件信号进入临界区。线程完成操作后SetEvent恢复信号状态。实验结果验证了事件同步机制的有效性:三个线程严格顺序执行(1→2→3),每个线程完整执行临界区操作后才会激活下一个线程,共享计数器依次累加为1→3→6,最终值6(1+2+3)证明资源操作安全可靠。

3.  改写课上的例5,用互斥量代替事件实现线程间的同步。

(1)实验代码

001

002

003

004

005

006

007

008

009

010

011

012

013

014

015

016

017

018

019

020

021

022

023

024

025

026

027

028

029

030

031

032

033

034

035

036

037

038

039

040

041

042

043

044

045

046

047

048

049

050

051

052

053

054

055

056

057

058

059

060

061

062

063

064

065

066

067

068

069

070

071

072

073

074

075

076

077

078

079

080

081

082

083

084

085

086

087

088

089

090

091

092

093

094

095

096

097

098

099

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

#include <windows.h>

#include <iostream>

using namespace std;

 

#define THREAD_INSTANCE_NUMBER 3

 

// 使用事件对象替代互斥量

HANDLE g_hEvent;

LONG g_lCounter = 0// 共享资源

 

DWORD WINAPI ThreadProc(LPVOID pData) {

    int threadNum = *(int*)pData;

 

    cout << "线程" << threadNum << ": 等待进入临界区..." << endl;

 

    // 等待事件信号(替代互斥量等待)

    WaitForSingleObject(g_hEvent, INFINITE);

 

    // 临界区开始

    cout << "线程" << threadNum << ": 进入临界区" << endl;

    cout << "  原始计数器值: " << g_lCounter << endl;

 

    // 操作共享资源

    g_lCounter += threadNum;

    Sleep(500); // 模拟处理时间

 

    cout << "  更新后计数器值: " << g_lCounter << endl;

 

    // 临界区结束

    cout << "线程" << threadNum << ": 离开临界区\n" << endl;

 

    // 触发事件允许下一个线程进入

    SetEvent(g_hEvent);

    return 0;

}

 

int main() {

    HANDLE hThreads[THREAD_INSTANCE_NUMBER];

    int threadIDs[THREAD_INSTANCE_NUMBER] = { 1, 2, 3 };

 

    // 创建手动重置事件(初始有信号)

    g_hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);

    if (!g_hEvent) {

        cerr << "创建事件失败!" << endl;

        return 1;

    }

 

    // 创建线程

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++) {

        threadIDs[i] = i + 1;

        hThreads[i] = CreateThread(NULL, 0, ThreadProc, &threadIDs[i], 0, NULL);

        if (!hThreads[i]) {

            cerr << "创建线程" << threadIDs[i] << "失败" << endl;

        }

        Sleep(100); // 延迟创建以观察等待过程

    }

 

    // 等待所有线程完成

    WaitForMultipleObjects(THREAD_INSTANCE_NUMBER, hThreads, TRUE, INFINITE);

 

    // 清理资源

    for (int i = 0; i < THREAD_INSTANCE_NUMBER; i++)

        CloseHandle(hThreads[i]);

 

    CloseHandle(g_hEvent);

 

    cout << "最终计数器值: " << g_lCounter << endl;

    cout << "程序正常退出" << endl;

    return 0;

}#include <windows.h>

#include <iostream>

using namespace std;

 

// 使用互斥量替代事件

HANDLE hMutexWrite, hMutexRead;

bool g_bDataReady = false;  // 数据准备标志

 

DWORD WINAPI ReadThread(LPVOID param) {

    // 等待写互斥量释放(表示数据已准备好)

    WaitForSingleObject(hMutexWrite, INFINITE);

 

    cout << "读取线程:处理数据中..." << endl;

    Sleep(800);  // 模拟读取耗时

 

    // 标记数据已处理完成

    g_bDataReady = true;

    cout << "读取线程:数据处理完成!" << endl;

 

    return 0;

}

 

DWORD WINAPI WriteThread(LPVOID param) {

    cout << "写入线程:生成数据中..." << endl;

    Sleep(1200);  // 模拟写入耗时

    cout << "写入线程:数据准备就绪!" << endl;

 

    // 释放写互斥量,允许读取线程开始

    ReleaseMutex(hMutexWrite);

    return 0;

}

 

int main() {

    // 创建互斥量(写互斥量初始由主线程拥有)

    hMutexWrite = CreateMutex(NULL, TRUE, NULL);  // 初始锁定

    hMutexRead = CreateMutex(NULL, FALSE, NULL);   // 初始无信号

 

    if (!hMutexWrite || !hMutexRead) {

        cerr << "互斥量创建失败!" << endl;

        return 1;

    }

 

    // 创建线程

    HANDLE hThreads[2];

    hThreads[0] = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);

    hThreads[1] = CreateThread(NULL, 0, WriteThread, NULL, 0, NULL);

 

    if (!hThreads[0] || !hThreads[1]) {

        cerr << "线程创建失败!" << endl;

        return 1;

    }

 

    // 释放写互斥量,允许写入线程开始

    ReleaseMutex(hMutexWrite);

 

    // 等待读取线程结束

    WaitForSingleObject(hThreads[0], INFINITE);

    cout << "\n主线程:收到完成信号,程序结束" << endl;

 

    // 清理资源

    for (auto h : hThreads) CloseHandle(h);

    CloseHandle(hMutexWrite);

    CloseHandle(hMutexRead);

 

    return 0;

}

(2)实验结果与分析

最后的实验结果展示正确的执行顺序:写入线程首先完成数据生成,释放互斥量后读取线程开始处理数据,最后主线程收到完成信号退出,完美复现了原始事件对象的同步功能,验证了互斥量作为线程同步机制的灵活性。

图2-3 例5 改写1

4. 改写课上的例5,用信号量代替事件实现线程间的同步。

(1)实验代码

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

#include <windows.h>

#include <iostream>

using namespace std;

 

// 使用信号量替代事件

HANDLE hSemRead;   // 表示数据已准备好

HANDLE hSemFinish; // 表示数据处理完成

 

DWORD WINAPI ReadThread(LPVOID param) {

    // 等待数据准备信号

    WaitForSingleObject(hSemRead, INFINITE);

     

    cout << "读取线程:正在处理数据..." << endl;

    Sleep(800);  // 模拟读取耗时

    cout << "读取线程:数据处理完成!" << endl;

     

    // 触发完成信号

    ReleaseSemaphore(hSemFinish, 1, NULL);

    return 0;

}

 

DWORD WINAPI WriteThread(LPVOID param) {

    cout << "写入线程:开始生成数据..." << endl;

    Sleep(1200);  // 模拟写入耗时

    cout << "写入线程:数据准备就绪!" << endl;

     

    // 触发数据准备信号

    ReleaseSemaphore(hSemRead, 1, NULL);

    return 0;

}

 

int main() {

    // 创建信号量(初始计数为0,最大计数为1

    hSemRead = CreateSemaphore(NULL, 0, 1, NULL);

    hSemFinish = CreateSemaphore(NULL, 0, 1, NULL);

     

    if (!hSemRead || !hSemFinish) {

        cerr << "信号量创建失败!" << endl;

        return 1;

    }

 

    // 创建线程

    HANDLE hThreads[2];

    hThreads[0] = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);

    hThreads[1] = CreateThread(NULL, 0, WriteThread, NULL, 0, NULL);

     

    if (!hThreads[0] || !hThreads[1]) {

        cerr << "线程创建失败!" << endl;

        return 1;

    }

 

    // 等待完成信号

    WaitForSingleObject(hSemFinish, INFINITE);

    cout << "\n主线程:收到完成信号,程序结束" << endl;

 

    // 清理资源

    for (auto h : hThreads) CloseHandle(h);

    CloseHandle(hSemRead);

    CloseHandle(hSemFinish);

     

    return 0;

}

(2)实验结果与分析

原始代码使用事件对象实现线程同步链(写入→读取→完成),我们现在改用信号量实现相同功能:创建两个信号量hSemRead和hSemFinish(初始计数为0),写入线程完成工作后通过ReleaseSemaphore增加hSemRead计数,触发读取线程执行,读取线程完成后通过ReleaseSemaphore增加hSemFinish计数,通知主线程。最后主线程通过WaitForSingleObject等待hSemFinish信号。实验结果保持原始执行顺序:写入线程首先完成数据生成,触发读取线程执行,最后主线程收到完成信号退出。

图2-4 例5 改写2


实验三 蒙特卡洛法并行求解Pi值

一. 实验目的与要求

    了解蒙特卡洛求π的串行和并行算法。

二. 实验环境及软件

硬件环境:。。。,OS:,软件工具:VC

三.实验内容

设计并实现蒙特卡洛求π的串行和并行算法。

四. 实验代码和结果分析

图3-1 串行估计

图3-2 并行估计

实验结果显示:在相同样本数下,串行版本估计的 π 为 3.14233,用时 0.407 秒;并行版本估计为 3.14204,用时反而更长,为 0.825038 秒。尽管并行计算通常能提升性能,但在本实验中由于线程创建和随机数引擎初始化等开销,反而导致并行效率不如串行。

串行估计:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

#include <iostream>

#include <cstdlib>

#include <ctime>

 

double monte_carlo_pi_serial(int num_samples) {

    int inside_circle = 0;

    for (int i = 0; i < num_samples; ++i) {

        double x = (double)rand() / RAND_MAX;

        double y = (double)rand() / RAND_MAX;

        if (x * x + y * y <= 1.0) {

            inside_circle++;

        }

    }

    return 4.0 * inside_circle / num_samples;

}

 

int main() {

    int samples = 10000000;

    std::srand(time(NULL));

    clock_t start = clock();

    double pi = monte_carlo_pi_serial(samples);

    clock_t end = clock();

 

    std::cout << "串行估计 π: " << pi << std::endl;

    std::cout << "耗时: " << (double)(end - start) / CLOCKS_PER_SEC << " 秒" << std::endl;

 

    return 0;

}

并行估计:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

#include <iostream>

#include <random>

#include <ctime>

#include <omp.h>

 

double monte_carlo_pi_parallel(int num_samples) {

    int inside_circle = 0;

 

    #pragma omp parallel

    {

        std::random_device rd;

        std::mt19937 gen(rd() + omp_get_thread_num());

        std::uniform_real_distribution<> dis(0.0, 1.0);

        int local_count = 0;

 

        #pragma omp for

        for (int i = 0; i < num_samples; ++i) {

            double x = dis(gen);

            double y = dis(gen);

            if (x * x + y * y <= 1.0) {

                local_count++;

            }

        }

 

        #pragma omp atomic

        inside_circle += local_count;

    }

 

    return 4.0 * inside_circle / num_samples;

}

 

int main() {

    int samples = 10000000;

    double start = omp_get_wtime();

    double pi = monte_carlo_pi_parallel(samples);

    double end = omp_get_wtime();

 

    std::cout << "并行估计 π: " << pi << std::endl;

    std::cout << "耗时: " << end - start << " 秒" << std::endl;

 

    return 0;

}


实验四 多核并行排序实验

一. 实验目的与要求

1、熟悉冒泡序的串行算法

2、熟悉冒泡排序的并行算法

3、实现冒泡排序的串行和并行算法,并比较运行时间  

二. 实验环境及软件

OS:同上,软件工具:VS

三. 实验内容

1、冒泡排序的基本思想

2、冒泡排序算法的性能

3、设计并实现冒泡排序算法的串行和并行程序

四. 实验代码和结果分析

图4-1 串行排序

图4-2 并行排序

实验结果显示,串行冒泡排序耗时 0.535 秒,并行排序耗时 0.50536 秒,性能提升非常有限。这是因为冒泡排序本身的数据依赖性强,难以实现高效并行化,同时线程创建与同步的开销也抵消了部分加速效果。因此,在数据规模较小时,并行冒泡排序难以体现优势。

串行排序代码:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

#include <iostream>

#include <vector>

#include <cstdlib>

#include <ctime>

 

void bubble_sort_serial(std::vector<int>& arr) {

    int n = arr.size();

    for (int i = 0; i < n - 1; ++i)

        for (int j = 0; j < n - 1 - i; ++j)

            if (arr[j] > arr[j + 1])

                std::swap(arr[j], arr[j + 1]);

}

 

int main() {

    const int N = 10000;

    std::vector<int> data(N);

    srand(time(0));

    for (int& x : data) x = rand();

 

    clock_t start = clock();

    bubble_sort_serial(data);

    clock_t end = clock();

 

    std::cout << "串行排序完成,耗时: " << (double)(end - start) / CLOCKS_PER_SEC << " 秒\n";

    return 0;

}

并行排序代码:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

#include <iostream>

#include <vector>

#include <cstdlib>

#include <ctime>

#include <omp.h>

 

void bubble_sort_parallel(std::vector<int>& arr) {

    int n = arr.size();

    for (int i = 0; i < n; ++i) {

        // 奇偶轮换

        #pragma omp parallel for

        for (int j = (i % 2); j < n - 1; j += 2) {

            if (arr[j] > arr[j + 1]) {

                std::swap(arr[j], arr[j + 1]);

            }

        }

    }

}

 

int main() {

    const int N = 10000;

    std::vector<int> data(N);

    srand(time(0));

    for (int& x : data) x = rand();

 

    double start = omp_get_wtime();

    bubble_sort_parallel(data);

    double end = omp_get_wtime();

 

    std::cout << "并行排序完成,耗时: " << end - start << " 秒\n";

    return 0;

}

选做实验 矩阵乘法并行化

一. 实验目的与要求

1.理解传统矩阵乘法的计算方式与其计算复杂度;

2.掌握使用 OpenMP 对矩阵乘法进行并行化的基本方法;

3.比较串行与并行矩阵乘法在多核环境下的运行时间与性能差异;

4.提高在多核编程环境中优化计算密集型任务的能力。

二. 实验环境及软件

同上

三. 实验内容

1.回顾并实现串行矩阵乘法(传统三重循环);

2.使用 OpenMP 对矩阵乘法的外层循环进行并行处理;

3.对比并分析串行与并行实现的运行时间;

4.探讨在更大规模矩阵下并行优化的效果与可能的性能瓶颈。

四. 实验代码和结果

图5-1 串行矩阵乘法结果

图5-2 并行矩阵乘法结果

在本次实验中,矩阵规模为 500×500,串行矩阵乘法耗时 2.14 秒,而使用 OpenMP 并行优化后的版本耗时反而上升至 2.36118 秒。分析其原因,主要包括线程调度和创建开销、使用二维 vector 带来的内存访问不连续性、以及在较小规模数据下并行粒度不足、缓存效率不高等问题。这表明,在多核程序设计中,并行优化应结合合理的数据结构、任务粒度和 cache 友好策略,否则并不能带来预期的加速效果。

串行矩阵乘法代码:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

#include <iostream>

#include <vector>

#include <ctime>

 

void matrix_multiply_serial(const std::vector<std::vector<int>>& A,

                            const std::vector<std::vector<int>>& B,

                            std::vector<std::vector<int>>& C, int N) {

    for (int i = 0; i < N; ++i)

        for (int j = 0; j < N; ++j)

            for (int k = 0; k < N; ++k)

                C[i][j] += A[i][k] * B[k][j];

}

 

int main() {

    const int N = 500;

    std::vector<std::vector<int>> A(N, std::vector<int>(N, 1));

    std::vector<std::vector<int>> B(N, std::vector<int>(N, 1));

    std::vector<std::vector<int>> C(N, std::vector<int>(N, 0));

 

    clock_t start = clock();

    matrix_multiply_serial(A, B, C, N);

    clock_t end = clock();

 

    std::cout << "串行矩阵乘法完成,耗时: "

              << (double)(end - start) / CLOCKS_PER_SEC << " 秒\n";

    return 0;

}

并行矩阵代码:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

#include <iostream>

#include <vector>

#include <omp.h>

 

void matrix_multiply_parallel(const std::vector<std::vector<int>>& A,

                               const std::vector<std::vector<int>>& B,

                               std::vector<std::vector<int>>& C, int N) {

    #pragma omp parallel for

    for (int i = 0; i < N; ++i)

        for (int j = 0; j < N; ++j)

            for (int k = 0; k < N; ++k)

                C[i][j] += A[i][k] * B[k][j];

}

 

int main() {

    const int N = 500;

    std::vector<std::vector<int>> A(N, std::vector<int>(N, 1));

    std::vector<std::vector<int>> B(N, std::vector<int>(N, 1));

    std::vector<std::vector<int>> C(N, std::vector<int>(N, 0));

 

    double start = omp_get_wtime();

    matrix_multiply_parallel(A, B, C, N);

    double end = omp_get_wtime();

 

    std::cout << "并行矩阵乘法完成,耗时: "

              << end - start << " 秒\n";

    return 0;

}

http://www.lqws.cn/news/463141.html

相关文章:

  • 数据分析核心指标体系:从求和、计数到比较的全维度计算方法
  • 一站式了解责任链模式
  • Qt实战:自定义二级选项框 | 附完整源码
  • 【Linux第四章】gcc、makefile、git、GDB
  • 【日志系统-时间戳】
  • 告别线程爆炸:我如何用 Spring WebFlux 构建一个端到端响应式应用
  • ad24智能pdf输出的装配图没有四个边角那里的圆孔
  • 面试题-ts中的typeof
  • 读者写者问题与读写锁自旋锁
  • OpenAI与微软的未来合作之路:充满挑战的AI竞赛与共赢
  • STM32F103C8T6 学习笔记摘要(二)
  • Knife4j 使用详解
  • (详细介绍)线性代数中的零空间(Null Space)
  • GitHub Copilot快捷键
  • JVM(8)——详解分代收集算法
  • linux生产环境下根据关键字搜索指定日志文件命令
  • Android多进程数据共享:SharedPreferences替代方案详解
  • RocketMQ--为什么性能不如Kafka?
  • 黑马头条-数据管理平台
  • Codeforces Round 1028 (Div. 2) A-C
  • ByteMD Markdown编辑器详细解释修改编辑器默认样式(高度300px)
  • Sublime text启用vim
  • 力扣刷题(第六十四天)
  • 咨询大师——解读96页麦肯锡金字塔原理【附全文阅读】
  • Qt输入数据验证的方法
  • 服务器架构---三高是什么
  • Ruby 范围(Range)
  • 如何用 eBPF 实现 Kubernetes 网络可观测性?实战指南
  • DM8故障分析工具-AWR报告
  • PY32学习(2)-搭建Keil环境