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

《TCP/IP网络编程》学习笔记 | Chapter 20:Windows 中的线程同步

《TCP/IP网络编程》学习笔记 | Chapter 20:Windows 中的线程同步

  • 《TCP/IP网络编程》学习笔记 | Chapter 20:Windows 中的线程同步
    • 用户模式和内核模式
      • 用户模式同步
      • 内核模式同步
    • 基于 CRITICAL_SECTION 的同步
    • 内核模式的同步方法
      • 基于互斥量对象的同步
      • 基于信号量对象的同步
      • 基于事件对象的同步
    • Windows 平台下实现多线程服务器端
    • 习题
      • (1)关于 Windows 操作系统的用户模式和内核模式的说法正确的是?
      • (2)判断下列关于用户模式同步和内核模式同步描述的正误。
      • (3)本章示例SyncSema_win.c 的 Read 函数中,退出临界区需要较长时间,请给出解决方案并实现。
      • (4)请将本章 SyncEvent_win.c 示例改为基于信号量的同步方式,并得出相同运行结果。

《TCP/IP网络编程》学习笔记 | Chapter 20:Windows 中的线程同步

用户模式和内核模式

Windows操作系统的运行方式是“双模式操作”(Dual-mode Operation):

  • 用户模式(User mode):运行应用程序的基本模式,禁止访问物理设备,而且会限制访问的内存区域。
  • 内核模式(Kernal mode):操作系统运行时的模式,不仅不会限制访问的内存区域,而且访问的硬件设备也不会受限。

实际上,在应用程序运行过程中,Windows操作系统不会一直停留在用户模式,而是在用户模式和内核模式之间切换。

例如,可以在Windows中创建线程。虽然创建线程的请求是由应用程序的函数调用完成,但实际创建线程的是操作系统。因此,创建线程的过程中无法避免向内核模式的转换。

定义这2种模式主要是为了提高安全性。应用程序的运行时错误会破坏操作系统及各种资源。特别是C/C++可以进行指针运算,很容易发生这类问题。例如,因为错误的指针运算覆盖了操作系统中存有重要数据的内存区域,这很可能引起操作系统崩溃。但实际上各位从未经历过这类事件,因为用户模式会保护与操作系统有关的内存区域。因此,即使遇到错误的指针运算也仅停止应用程序的运行,而不会影响操作系统。

总之,像线程这种伴随着内核对象创建的资源创建过程中,都要默认经历如下模式转换过程:用户模式→内核模式→用户模式。

从用户模式切换到内核模式是为了创建资源,从内核模式再次切换到用户模式是为了执行应用程序的剩余部分。不仅是资源的创建,与内核对象有关的所有事务都在内核模式下进行。

模式切换对系统而言其实也是一种负担,频繁的模式切换会影响性能。

用户模式同步

用户模式同步是用户模式下进行的同步,即无需操作系统的帮助而在应用程序级别进行的同步。

用户模式同步的最大优点是——速度快。无需切换到内核模式,仅考虑这一点也比经历内核模式切换的其他方法要快。而且使用方法相对简单,因此,适当运用用户模式同步并无坏处。

但因为这种同步方法不会借助操作系统的力量,其功能上存在一定局限性。稍后将介绍属于用户模式同步的、基于“CRITICAL_SECTION”的同步方法。

内核模式同步

下面给出内核模式同步的优点。

  • 比用户模式同步提供的功能更多。
  • 可以指定超时,防止产生死锁。

因为都是通过操作系统的帮助完成同步的,所以提供更多功能。特别是在内核模式同步中,可以跨越进程进行线程同步。因为内核对象并不属于某一进程,而是操作系统拥有并管理的。

与此同时,由于无法避免用户模式和内核模式之间的切换,所以性能上会受到一定影响。

基于 CRITICAL_SECTION 的同步

基于 CRITICAL_SECTION 的同步中将创建并运用“CRITICAL_SECTION对象”,但这并非内核对象。与其他同步对象相同,它是进入临界区的一把“钥匙”。因此,为了进入临界区,需要得到 CRITICAL_SECTION 对象这把“钥匙”。相反,离开时应上交 CRITICAL_SECTION 对象。

下面介绍 CRITICAL_SECTION 对象的初始化及销毁相关函数。

#include<windows.h>void InitilizerCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

参数:

  • IpCriticalSection:InitilizerCriticalSection 函数中传入需要初始化的 CRITICAL_SECTION 对象的地址值,DeleteCriticalSection 函数中传入需要解除的 CRITICAL_SECTION 对象的地址值。

上述函数的参数类型 LPCRITICAL_SECTION 是 CRITICAL_SECTION 指针类型。另外 DeleteCriticalSection 函数并不销毁CRITICAL_SECTION 对象。该函数的作用是销毁 CRITICAL_SECTION 对象相关的资源。

接下来介绍获取(拥有)及释放 CRITICAL_SECTION 对象的函数,可以简单理解为获取和释放“钥匙”的函数。

#include<windows.h>void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

参数:

  • IpCriticalSection:获取(拥有)及释放 CRITICAL_SECTION 对象的地址值。

与 Linux 部分中介绍过的互斥量类似,相信大部分人仅靠这些函数介绍也能写出示例程序。

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>#define NUM_THREAD 50
unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDes(void *arg);long long num = 0;
CRITICAL_SECTION cs;int main(int argc, char *argv[])
{HANDLE tHandles[NUM_THREAD];int i;InitializeCriticalSection(&cs);for (i = 0; i < NUM_THREAD; i++){if (i % 2)tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);elsetHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);}WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);DeleteCriticalSection(&cs);printf("result: %lld \n", num);system("pause");return 0;
}unsigned WINAPI threadInc(void *arg)
{int i;EnterCriticalSection(&cs);for (i = 0; i < 50000000; i++)num += 1;LeaveCriticalSection(&cs);return 0;
}unsigned WINAPI threadDes(void *arg)
{int i;EnterCriticalSection(&cs);for (i = 0; i < 50000000; i++)num -= 1;LeaveCriticalSection(&cs);return 0;
}

运行结果:

在这里插入图片描述

程序将整个循环纳入临界区,可以减少运行时间。

内核模式的同步方法

基于互斥量对象的同步

基于互斥量(Mutual Exclusion)对象的同步方法与基于 CRITICAL_SECTION 对象的同步方法类似,因此,互斥量对象同样可以理解为“钥匙”。

首先介绍创建互斥量对象的函数:

#include<windows.h>HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName
);

参数:

  • lpMutexAttributes:传递安全相关的配置信息,使用默认安全设置时可以传递 NULL。
  • blnitialOwner:如果为 TRUE,则创建出的互斥量对象属于调用该函数的线程,同时进入 non-signaled 状态;如果为 FALSE,则创建出的互斥量对象不属于任何线程,此时状态为 signaled。
  • IpName:用于命名互斥量对象。传入 NULL 创建无名的互斥量对象。

成功时返回创建的互斥量对象句柄,失败时返回 NULL。

从上述参数说明中可以看到,如果互斥量对象不属于任何拥有者,则将进入 signaled 状态。利用该特点进行同步。

另外,互斥量属于内核对象,所以通过如下函数销毁:

#include<windows.h>BOOL CloseHandle(HANDLE hObject);

参数:

  • hObject:要销毁的内核对象的句柄。

成功时返回 TRUE,失败时返回 FALSE。

上述函数是销毁内核对象的函数,所以同样可以销毁即将介绍的信号量及事件。下面介绍获取和释放互斥量的函数,但我认为只需介绍释放的函数,因为获取是通过各位熟悉的 WaitForSingleObject 函数完成的。

#include<windows.h>BOOL ReleaseMutex(HANDLE hMutex);

参数:

  • hMutex:需要释放的互斥量对象句柄。

成功时返回 TRUE,失败时返回 FALSE。

接下来分析获取和释放互斥量的过程。互斥量被某一线程获取时(拥有时)为 non-signaled 状态,释放时(未拥有时)进入 signaled 状态。因此,可以使用 WaitForSingleObject 函数验证互斥量是否已分配。该函数的调用结果有如下 2 种。

  • 调用后进入阻塞状态:互压量对象已被其他线程获取,现处于 non-signaled 状态。
  • 调用后直接返回:其他线程未占用互斥量对象,现处于 signaled 状态。

互斥量在 WaitForSingleObject 函数返回时自动进入 non-signaled 状态,因为它是第 19 章介绍过的"auto-reset"模式的内核对象。结果,WaitForSingleObject 函数为申请互斥量时调用的函数。因此,基于互斥量的临界区保护代码如下:

WaitForsingleobject(hMutex, INFINITE);
// 临界区的开始
// ......
// 临界区的结束
ReleaseMutex(hMutex);

WaitForSingleObject 函数使互斥量进入 non-signaled 状态,限制访问临界区,所以相当于临界区的门禁系统。相反,ReleaseMutex 函数使互斥量重新进入 signaled 状态,所以相当于临界区的出口。

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>#define NUM_THREAD 50
unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDes(void *arg);long long num = 0;
HANDLE hMutex;int main(int argc, char *argv[])
{HANDLE tHandles[NUM_THREAD];int i;hMutex = CreateMutex(NULL, FALSE, NULL);for (i = 0; i < NUM_THREAD; i++){if (i % 2)tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);elsetHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);}WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);CloseHandle(hMutex);printf("result: %lld \n", num);system("pause");return 0;
}unsigned WINAPI threadInc(void *arg)
{int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < 50000000; i++)num += 1;ReleaseMutex(hMutex);return 0;
}unsigned WINAPI threadDes(void *arg)
{int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < 50000000; i++)num -= 1;ReleaseMutex(hMutex);return 0;
}

运行结果:

在这里插入图片描述

基于信号量对象的同步

Windows 中基于信号量对象的同步也与 Linux 下的信号量类似,二者都是利用名为“信号量值”的整数值完成同步的,而且该值都不能小于 0。当然,Windows 的信号量值注册于内核对象。

下面介绍创建信号量对象的函数,其销毁同样是利用 CloseHandle 函数进行的。

#include <windows.h>HANDLE Createsemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,LONG lInitialCount,LONG lMaximumCount,LPCTSTR lpName
);

参数:

  • IpSemaphoreAttributes:安全配置信息,采用默认安全设置时传递 NULL。
  • lInitialCount:指定信号量的初始值,应大于 0 小于 lMaximumCount。
  • IMaximumCount:信号量的最大值。该值为 1 时,信号量变为只能表示 0 和 1 的二进制信号量。
  • lpName:用于命名信号量对象,传递 NULL 时创建无名的信号量对象。

成功时返回创建的信号量对象的句柄,失败时返回 NULL。

向 lInitialCount 参数传递 0 时,创建 non-signaled 状态的信号量对象。而向 IMaximumCount 传入 3 时,信号量最大值为 3,因此可以实现 3 个线程同时访问临界区时的同步。

可以利用“信量值为 0 时进入 non-signaled 状态,大于 0 时进入 signaled 状态”的特性进行同步。

下面介绍释放信号量对象的函数:

#include <windows.h>BOOL ReleaseSemaphore(HANDLE hSemaphore,LONG lReleaseCount,LPLONG lpPreviouscount
);

参数:

  • Semaphore:传递需要释放的信号量对象.
  • IReleaseCount:释放意味着信号量值的增加,通过该参数可以指定增加的值。超过最大值则不增加,返回 FALSE。
  • IpPreviousCount:用于保存修改之前值的变量地址,不需要时可传递 NULL。

成功时返回 TRUE,失败时返回 FALSE。

信号量对象的值大于 0 时成为 signaled 状态,为 0 时成为 non-signaled 状态。因此,调用 WaitForSingleObject 函数时,信号量大于 0 的情况才会返回,返回的同时将信量值减 1。可以通过如下程序结构保护临界区。

WaitForSingleObject(hSemaphore, INFINITE);
// 临界区的开始
// ......
// 临界区的结束
ReleaseSemaphore(hSemaphore, 1, NULL);

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>unsigned WINAPI read(void *arg);
unsigned WINAPI accu(void *arg);static HANDLE sem_one;
static HANDLE sem_two;
static int num;int main(int argc, char const *argv[])
{HANDLE hThread1, hThread2;sem_one = CreateSemaphore(NULL, 0, 1, NULL);sem_two = CreateSemaphore(NULL, 1, 1, NULL);hThread1 = (HANDLE)_beginthreadex(NULL, 0, read, NULL, 0, NULL);hThread2 = (HANDLE)_beginthreadex(NULL, 0, accu, NULL, 0, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);CloseHandle(sem_one);CloseHandle(sem_two);system("pause");return 0;
}unsigned WINAPI read(void *arg)
{int i;for (i = 0; i < 5; i++){fputs("Input num: ", stdout);WaitForSingleObject(sem_two, INFINITE);scanf("%d", &num);ReleaseSemaphore(sem_one, 1, NULL);}return 0;
}unsigned WINAPI accu(void *arg)
{int sum = 0, i;for (i = 0; i < 5; i++){WaitForSingleObject(sem_one, INFINITE);sum += num;ReleaseSemaphore(sem_two, 1, NULL);}printf("Result: %d \n", sum);return 0;
}

运行结果:

在这里插入图片描述

在循环内部构件临界区,起到尽可能缩小临界区的作用,尽量提高程序性能。

基于事件对象的同步

事件同步对象与前 2 种同步方法相比有很大不同,区别就在于,该方式下创建对象时,在自动以 non-signaled 状态运行的“auto-reset”模式和与之相反的“manual-reset”模式中任选其一。而事件对象的主要特点是可以创建“manual-reset”模式的对象。

首先介绍用于创建事件对象的函数:

#include <windows.h>HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,LPCTSTR lpName
);

参数:

  • IpEventAttributes:安全配置相关参塑,采用默认安全配置时传入 NULL。
  • bManualReset:传入 TRUE 时创建“manual-reset”模式的事件对象,传入 FALSE 时创建“auto-reset”模式的事件对象。
  • bInitialState:传入 TRUE 时创建 signaled 状态的事件对象,传入 FALSE 时创建 non-signaled态的事件对象。
  • IpName:用于命名事件对象。传递 NULL 时创建无名的事件对象。

成功时返回创建的事件对象句柄,失败时返回 NULL。

相信各位也发现了,上述函数中需要重点关注的是第二个参数。传人 TRUE 时创建“manual-reset”模式的事件对象,此时即使 WaitForSingleObject 函数返回也不会回到 non-signaled 状态。

因此,在这种情况下,需要通过如下 2 个函数明确更改对象状态:

#include <windows.h>BOOL ResetEvent(HANDLE hEvent); // 设置为 non-signaled 状态
BOOL SetEvent(HANDLE hEvent);   // 设置为 signaled 状态

成功时返回 TRUE,失败时返回 FALSE。

传递事件对象句柄并希望改为 non-signaled 状态时,应调用 ResetEvent 函数。如果希望改为 signaled 状态,则可以调用 SetEvent 函数。

示例程序:

在这里插入代码片

运行结果:

在这里插入图片描述

读入字符串后将事件改成 signaled 状态,等待的 2 个线程将摆脱等待状态,开始执行。最后还是把事件对象的状态改为 non-signaled 状态。

Windows 平台下实现多线程服务器端

服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>#define BUF_SIZE 100
#define MAX_CLNT 256unsigned WINAPI HandleClnt(void *arg);
void SendMsg(char *msg, int len);
void ErrorHandling(char *msg);int clntCnt = 0;
SOCKET clntSocks[MAX_CLNT];
HANDLE hMutex;int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET hServSock, hClntSock;SOCKADDR_IN servAdr, clntAdr;int clntAdrSz;HANDLE hThread;if (argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHandling("WSAStartup() error!");hMutex = CreateMutex(NULL, FALSE, NULL);hServSock = socket(PF_INET, SOCK_STREAM, 0);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = htonl(INADDR_ANY);servAdr.sin_port = htons(atoi(argv[1]));if (bind(hServSock, (SOCKADDR *)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)ErrorHandling("bind() error");if (listen(hServSock, 5) == SOCKET_ERROR)ErrorHandling("listen() error");while (1){clntAdrSz = sizeof(clntAdr);hClntSock = accept(hServSock, (SOCKADDR *)&clntAdr, &clntAdrSz);WaitForSingleObject(hMutex, INFINITE);clntSocks[clntCnt++] = hClntSock;ReleaseMutex(hMutex);hThread = (HANDLE)_beginthreadex(NULL, 0, HandleClnt, (void *)&hClntSock, 0, NULL);printf("Connected client IP: %s \n", inet_ntoa(clntAdr.sin_addr));}closesocket(hServSock);WSACleanup();return 0;
}unsigned WINAPI HandleClnt(void *arg)
{SOCKET hClntSock = *((SOCKET *)arg);int strLen = 0, i;char msg[BUF_SIZE];while ((strLen = recv(hClntSock, msg, sizeof(msg), 0)) != 0)SendMsg(msg, strLen);WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < clntCnt; i++){if (hClntSock == clntSocks[i]){while (i++ < clntCnt - 1)clntSocks[i] = clntSocks[i + 1];break;}}clntCnt--;ReleaseMutex(hMutex);closesocket(hClntSock);return 0;
}void SendMsg(char *msg, int len)
{ // 发送给全部人int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < clntCnt; i++)send(clntSocks[i], msg, len, 0);ReleaseMutex(hMutex);
}void ErrorHandling(char *msg)
{fputs(msg, stderr);fputc('\n', stderr);exit(1);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>#define BUF_SIZE 100
#define NAME_SIZE 20unsigned WINAPI SendMsg(void *arg);
unsigned WINAPI RecvMsg(void *arg);
void ErrorHandling(char *msg);char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET hSock;SOCKADDR_IN servAdr;HANDLE hSndThread, hRcvThread;if (argc != 4){printf("Usage: %s <IP> <port> <name>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHandling("WSAStartup() error!");sprintf(name, "[%s]", argv[3]);hSock = socket(PF_INET, SOCK_STREAM, 0);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = inet_addr(argv[1]);servAdr.sin_port = htons(atoi(argv[2]));if (connect(hSock, (SOCKADDR *)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)ErrorHandling("connect() error");hSndThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void *)&hSock, 0, NULL);hRcvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void *)&hSock, 0, NULL);WaitForSingleObject(hSndThread, INFINITE);WaitForSingleObject(hRcvThread, INFINITE);closesocket(hSock);WSACleanup();return 0;
}unsigned WINAPI SendMsg(void *arg)
{SOCKET hSock = *((SOCKET *)arg);char nameMsg[NAME_SIZE + BUF_SIZE];while (1){fgets(msg, BUF_SIZE, stdin);if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")){closesocket(hSock);exit(0);}sprintf(nameMsg, "%s %s", name, msg);send(hSock, nameMsg, strlen(nameMsg), 0);}return 0;
}unsigned WINAPI RecvMsg(void *arg)
{int hSock = *((SOCKET *)arg);char nameMsg[NAME_SIZE + BUF_SIZE];int strLen;while (1){strLen = recv(hSock, nameMsg, NAME_SIZE + BUF_SIZE - 1, 0);if (strLen == -1)return -1;nameMsg[strLen] = '\0';fputs(nameMsg, stdout);}return 0;
}void ErrorHandling(char *msg)
{fputs(msg, stderr);fputc('\n', stderr);exit(1);
}

编译:

gcc chat_server_win.c -lwsock32 -o cserv
gcc chat_client_win.c -lwsock32 -o cclnt

运行结果:

在这里插入图片描述

习题

(1)关于 Windows 操作系统的用户模式和内核模式的说法正确的是?

a. 用户模式是应用程序运行的基本模式,虽然访问的内存空间没有限制,但无法访问物理设备。
b. 应用程序运行过程中绝对不会进人内核模式。应用程序只在用户模式中运行。
c. Windows 为了有效使用内存空间,分别定义了用户模式和内核模式。
d. 应用程序运行过程中也有可能切换到内核模式。只是切换到内核模式后,进程将一直保持该状态。

答:

c。

(2)判断下列关于用户模式同步和内核模式同步描述的正误。

  • 用户模式的同步中不会切换到内核模式。即非操作系统级别的同步。( √ )
  • 内核模式的同步是由操作系统提供的功能,比用户模式同步提供更多功能。( √ )
  • 需要在用户模式和内核模式之间切换,这是内核模式同步的缺点。( √ )
  • 除特殊情况外,原则上应使用内核模式同步。用户模式同步是操作系统提供内核模式同步机制前使用的同步方法。( × )

(3)本章示例SyncSema_win.c 的 Read 函数中,退出临界区需要较长时间,请给出解决方案并实现。

用一个数组存储输入的 5 个数字,将整个循环改为临界区,减少进入/离开临界区的次数。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>unsigned WINAPI read(void *arg);
unsigned WINAPI accu(void *arg);static HANDLE sem_one, sem_two;
static int arr[5];int main(int argc, char const *argv[])
{HANDLE hThread1, hThread2;sem_one = CreateSemaphore(NULL, 0, 1, NULL);sem_two = CreateSemaphore(NULL, 1, 1, NULL);hThread1 = (HANDLE)_beginthreadex(NULL, 0, read, NULL, 0, NULL);hThread2 = (HANDLE)_beginthreadex(NULL, 0, accu, NULL, 0, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);CloseHandle(sem_one);CloseHandle(sem_two);system("pause");return 0;
}unsigned WINAPI read(void *arg)
{WaitForSingleObject(sem_two, INFINITE);for (int i = 0; i < 5; i++){fputs("Input num: ", stdout);scanf("%d", &arr[i]);}ReleaseSemaphore(sem_one, 1, NULL);return 0;
}unsigned WINAPI accu(void *arg)
{int sum = 0;WaitForSingleObject(sem_one, INFINITE);for (int i = 0; i < 5; i++)sum += arr[i];ReleaseSemaphore(sem_two, 1, NULL);printf("Result: %d \n", sum);return 0;
}

运行结果:

在这里插入图片描述

(4)请将本章 SyncEvent_win.c 示例改为基于信号量的同步方式,并得出相同运行结果。

相关文章:

《TCP/IP网络编程》学习笔记 | Chapter 20:Windows 中的线程同步

《TCP/IP网络编程》学习笔记 | Chapter 20&#xff1a;Windows 中的线程同步 《TCP/IP网络编程》学习笔记 | Chapter 20&#xff1a;Windows 中的线程同步用户模式和内核模式用户模式同步内核模式同步 基于 CRITICAL_SECTION 的同步内核模式的同步方法基于互斥量对象的同步基于…...

MyBatis 中 #{} 和 ${} 的区别详解

目录 1. #{} 和 ${} 的基本概念 1.1 #{} 1.2 ${} 2. #{} 和 ${} 的工作原理 2.1 #{} 的工作原理 2.2 ${} 的工作原理 3.共同点&#xff1a;动态 SQL 查询 4. 区别&#xff1a;处理方式和适用场景 4.1 处理方式 4.2 适用场景 &#xff08;1&#xff09;#{} 的适用场景…...

C++学习之网盘项目单例模式

目录 1.知识点概述 2.单例介绍 3.单例饿汉模式 4.饿汉模式四个版本 5.单例类的使用 6.关于token的作用和存储 7.样式表使用方法 8.qss文件中选择器介绍 9.qss文件样式讲解和测试 10.qss美化登录界面补充 11.QHTTPMULTIPART类的使用 12.文件上传协议 13.文件上传协议…...

Lineageos 22.1(Android 15)制定应用强制横屏

一、前言 有时候需要系统的某个应用强制衡平显示&#xff0c;不管他是如何配置的。我们只需要简单的拿到top的Task下面的ActivityRecord&#xff0c;并判断包名来强制实现。 二、调整wms com.android.server.wm.DisplayRotation /*** Given an orientation constant, return…...

基于deepseek的智能语音客服【第四讲】封装milvus数据库连接池封装

通过工厂模式创建链接 static {// 创建连接池工厂BasePooledObjectFactory<MilvusServiceClient> factory new BasePooledObjectFactory<MilvusServiceClient>() {Overridepublic MilvusServiceClient create() throws Exception {return new MilvusServiceClient…...

【GeeRPC】项目总结:使用 Golang 实现 RPC 框架

文章目录 项目总结&#xff1a;使用 Golang 实现 RPC 框架谈谈 RPC 框架什么是 RPC 框架实现一个 RPC 框架需要什么&#xff1f;项目总结文章结构安排 Part1&#xff1a;消息编码编解码器的实现通信过程 Part2&#xff1a;服务端Accept&#xff1a;阻塞地等待连接请求并开启 go…...

人工智能在医疗影像诊断中的应用与挑战

引言 近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术在医疗领域的应用逐渐成为研究热点&#xff0c;尤其是在医疗影像诊断方面。AI技术的引入为医疗影像诊断带来了更高的效率和准确性&#xff0c;有望缓解医疗资源紧张的问题&#xff0c;同时为患者提供更优质的医疗…...

烧结银技术赋能新能源汽车超级快充与高效驱动

烧结银技术赋能新能源汽车超级快充与高效驱动 在新能源汽车领域&#xff0c;高压快充技术的突破与高功率密度驱动系统的创新正成为行业竞争的焦点。比亚迪于 2025 年发布的超级 e 平台&#xff0c;通过整合全域千伏高压架构、兆瓦级闪充技术及碳化硅&#xff08;SiC&#xff0…...

大模型幻觉产生的【九大原因】

知识问答推理幻觉产生的原因 1.知识库结构切割不合理 大段落切割向量化 切分太小可以实现更精准化的回复内&#xff0c;向量匹配相似度越高。检索内容碎片化严重、可能包含不符合内容的文本数据。切分太大内容资料更完整&#xff0c;但是会影响相似度&#xff0c;同时更消耗资…...

4小时速通shell外加100例

&#x1f525; Shell 基础——从入门到精通 &#x1f680; &#x1f331; 第一章&#xff1a;Shell&#xff0c;简单说&#xff01; &#x1f476; 什么是Shell&#xff1f;它到底能做什么&#xff1f;这章让你快速了解Shell的强大之处&#xff01; &#x1f476; 什么是Shell…...

AD(Altium Designer)更换PCB文件的器件封装

一、确定是否拥有想换的器件PCB封装 1.1 打开现有的原理图 1.2 确定是否拥有想换的器件PCB文件 1.2.1 如果有 按照1.3进行切换器件PCB封装 1.2.2 如果没有 按照如下链接进行添加 AD(Altium Designer)已有封装库的基础上添加器件封装-CSDN博客https://blog.csdn.net/XU15…...

Postgresql 删除数据库报错

1、删除数据库时&#xff0c;报错存在其他会话连接 ## 错误现象&#xff0c;存在其他的会话连接正在使用数据库 ERROR: database "cs" is being accessed by other users DETAIL: There is 1 other session using the database.2、解决方法 ## 终止被删除数据库下…...

人工智能时代——深度探索如何构建开放可控的专利生态体系

# 人工智能时代——深度探索如何构建开放可控的专利生态体系 引言&#xff1a;AI专利革命的战略抉择第一章 战略认知与基本原则1.1 人工智能专利革命的范式重构1.1.1 技术维度变革1.1.2 法律维度挑战1.1.3 文明安全的不可控风险 1.2 战略定位体系构建1.2.1 双循环治理框架的立体…...

✨【数据变形术:联合体在通信协议中的降维打击】✨

&#xff08;万字长文详解联合体的二进制魔法与工程实践&#xff09; &#x1f52e; 原理解析&#xff1a;内存空间的量子叠加态 文字叙述&#xff1a; 联合体&#xff08;union&#xff09;是C语言中最具魔法的数据结构&#xff0c;其所有成员共享同一块内存空间。这种特性使…...

docker compose部署minio报错

背景 部分服务使用docker-compose单节点编排&#xff0c;其中对象存储服务使用minio&#xff0c;在minio中配置了aksk后报错 Error: IAM sub-system is partially initialized, unable to write the IAM forma 解决 minio如果配置了aksk等iam类的配置则需要持久化存储到etcd…...

软件开发通用之状态机初认识-基本概念及简单应用

0 前言 在程序开发阶段&#xff08;其实也不限于程序&#xff0c;还包含硬件电路设计&#xff0c;协议设计等&#xff09;&#xff0c;无论使用何种语言&#xff0c;何种工具&#xff0c;何种系统&#xff0c;程序的运行必须符合开发者的预设逻辑&#xff0c;而单独通过大脑记…...

蓝桥杯 之 第27场月赛总结

文章目录 习题1.抓猪拿国一2.蓝桥字符3.蓝桥大使4.拳头对决 习题 比赛地址 1.抓猪拿国一 十分简单的签到题 print(sum(list(range(17))))2.蓝桥字符 常见的字符匹配的问题&#xff0c;是一个二维dp的问题&#xff0c;转化为对应的动态规划求解 力扣的相似题目 可以关注灵神…...

适配器模式 (Adapter Pattern)

适配器模式 (Adapter Pattern) 是一种结构型设计模式,它将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。 在现实生活中,适配器的例子随处可见,比如电源适配器,它将不同电压的电流转换为设备所需的电压,确保设备能正…...

操作系统WIN11无法出现WLAN图标(解决方案)

本人操作系统WIN11之后无网络图标 于是在设置里查看了一下&#xff0c;是网卡驱动没了 网上去下载一个驱动类软件自行处理即可。 本人使用手机USB网络连的电脑&#xff0c;然后发现网卡驱动凭空出现了&#xff0c;就很困惑&#xff0c;没有下载驱动就恢复了。...

HCL—我与虚拟机的爱恨情仇[特殊字符][特殊字符]‍[特殊字符]️

时隔了三周&#xff0c;我可能算是了解了虚拟机了吧。自从上一次的安装虚拟机&#xff0c;我与HCL、虚拟机就没有停止过纠缠。 为什么很多win11电脑使用不了HCL&#xff0c;或者无法启动HCL设备&#xff1f; 首先来解答&#xff0c;为什么很多win11电脑使用不了HCL&#xff0c…...

illustrate:一款蛋白/核酸结构快速渲染为“卡通风格”的小工具

本期向大家介绍一款蛋白/核酸结构快速渲染&#xff08;卡通风格&#xff09;的小工具——illustrate。放心&#xff01;本期完全不涉及代码&#xff0c;不折腾人&#xff0c;请放心食用。 结构渲染效果示例如下&#xff1a; PDB ID: 1ttt 该小工具适用绘制蛋白或复合物整体轮廓…...

Linux上位机开发实战(能用的开发板计算资源)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 大家所能想到的嵌入式上位机开发&#xff0c;如果是linux&#xff0c;同时涉及到嵌入式的话&#xff0c;一般都会认为是把pc linux的软件port到板子…...

kotlin 内联函数 inline

高阶函数实现的原理&#xff1a;函数类型其实是生成了一个对象 。 inline翻译成中文的意思就是内联&#xff0c;在kotlin里面inline被用来修饰函数&#xff0c;表明当前函数在编译时是以内嵌的形式进行编译的&#xff0c;从而减少了一层函数调用栈&#xff1a; inline fun fun…...

vue3配置代理实现axios请求本地接口返回PG库数据【前后端实操】

前端编写 安装 axios 如果当前未安装axios&#xff0c;可以执行如下指令安装 npm install axios配置代理 当前为基于Vite构建的项目&#xff0c;在 vite.config.ts 中配置代理&#xff0c;在defineConfig中新增server配置&#xff0c;主要关注两个点&#xff1a; 一、需要代…...

论文阅读:2023 arxiv Multiscale Positive-Unlabeled Detection of AI-Generated Texts

总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 Multiscale Positive-Unlabeled Detection of AI-Generated Texts https://arxiv.org/abs/2305.18149 https://www.doubao.com/chat/2114270649152258 https://github.com/YuchuanTi…...

【数学建模】最大最小值模型详解

数学建模中的最大最小值模型详解 文章目录 数学建模中的最大最小值模型详解引言最大最小值模型的基本概念最大化问题最小化问题 常见的求解方法1. 微积分法2. 线性规划3. 非线性规划4. 动态规划 实际应用案例案例1&#xff1a;生产规划问题案例2&#xff1a;投资组合优化 最大最…...

Camera2 实现重力感应四个方向调试相机预览

Camera2API 实现重力感应四个方向调试相机预览 文章目录 需求场景 需求实现setAspectRatio 设置显示长宽postScale postRotate 设置缩放和旋转manager.openCamera 打开相机startPreviewgetPreviewRequestBuilder 设置预览参数&#xff1a;createCaptureSession 预览准备工作set…...

C++::多态

目录 一.多态的概念 二.多态的定义及实现 二.1多态的构成条件 二.2虚函数 1.虚函数的写法 2.虚函数的重写/覆盖 3.协变 二.3析构函数的重写 二.4override和final关键字 ​编辑二.5重载/重写/隐藏的对比 三.多态的运行原理&#xff08;一部分&#xff09; 四.多态的常…...

278.缀点成线

1232. 缀点成线 - 力扣&#xff08;LeetCode&#xff09; class Solution {public boolean checkStraightLine(int[][] coordinates) {if(coordinates.length2){return true;}int xcoordinates[1][0]-coordinates[0][0];int ycoordinates[1][1]-coordinates[0][1];for(int i1;i…...

xssgame第8关注入详解

1.SVG利用实现xss攻击 1.代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>tes…...

《数据库原理》SQLServer期末复习_题型+考点

目录 题型&#xff1a; 一. 概况分析题&#xff08;5小题&#xff0c;每小题2分&#xff0c;共10分&#xff09; 二. 计算题&#xff08;3小题&#xff0c;每小题5分&#xff0c;共15分&#xff09; 三. 数据库设计&#xff08;2小题&#xff0c;每小题10分&#xff0c;共2…...

RK3588开发笔记-RTL8852wifi6模块驱动编译报错解决

目录 前言 一、问题背景 二、驱动编译 总结 前言 在基于 RK3588 进行开发,使用 RTL8852 WiFi6 模块时,遇到了一个让人头疼的驱动编译报错问题:“VFs_internal_I_am_really_a_filesystem_and_am_NoT_a_driver, but does”。经过一番摸索和尝试,最终成功解决了这个问题,在…...

机器学习算法实战——天气数据分析(主页有源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​​​ 1. 引言 天气数据分析是气象学和数据科学交叉领域的一个重要研究方向。随着大数据技术的发展&#xff0c;气象数据的采集、存储和分…...

java项目之基于ssm的毕业论文管理系统(源码+文档)

项目简介 毕业论文管理系统实现了以下功能&#xff1a; 本毕业论文管理系统主要实现的功能模块包括学生模块、导师模块和管理员模块三大部分&#xff0c;具体功能分析如下&#xff1a; &#xff08;1&#xff09;导师功能模块&#xff1a;导师注册登录后主要功能模块包括个人…...

【Vue3入门1】02- vue3的基本操作(上)

本文介绍vue3中的一些方法的操作。 目录 1. 绑定事件 v-on 2. 按键修饰符 3. 显示和隐藏 v-show 4. 条件渲染 v-if 5. 条件渲染if-else 1. 绑定事件 v-on 点击事件 v-on:click" 发生事件 " <body><div id"app">{{ msg }} <h2&g…...

Redis集群搭建和高可用方案(Java实现)

Redis集群搭建和高可用方案(Java实现) 我将详细介绍如何使用Java技术搭建Redis集群并实现高可用方案。 1. Redis集群架构概述 Redis集群可以通过以下几种方式实现: 主从复制Sentinel哨兵模式Redis Cluster集群模式2. 使用Java实现Redis集群连接 2.1 使用Jedis客户端 Je…...

【大模型算法工程】大模型应用工具化、忠诚度以及知识库场景下PDF双栏解析问题的讨论

1. 大模型时代应用工具化以及无忠诚度现象讨论 接触大模型久了&#xff0c;也慢慢探到一些大模型能力表现非常自然和突出的场景&#xff0c;比如AI搜索&#xff08;依赖大模型的理解总结能力&#xff09;、AI对话&#xff08;即chat&#xff0c;依赖大模型的生成能力&#xff0…...

Rust语言学习

Rust语言学习 通用编程概念所有权所有权引用和借用slice struct(结构体)定义并实例化一个结构体使用结构体方法语法 枚举 enums定义枚举match控制流运算符if let 简单控制流 使用包、Crate和模块管理不断增长的项目&#xff08;模块系统&#xff09;包和crate定义模块来控制作用…...

AI比人脑更强,因为被植入思维模型【16】反脆弱

毛选中就有言&#xff0c;不经历困难&#xff0c;我们就不会掌握战胜困难的方法。 这个世界纷繁复杂&#xff0c;不是强者总是运气好&#xff0c;而是他们能够失败后快速复原&#xff0c;不断找到战胜困难的方法。 定义 马斯洛需求层次模型是一种将人类需求从低到高按层次进…...

系统架构设计知识体系总结

1.技术选型 1.什么是技术选型&#xff1f; 技术选型是指评估和选择在项目或系统开发中使用的最合适的技术和工具的过程。这涉及考虑基于其能力、特性、与项目需求的兼容性、可扩展性、性能、维护和其他因素的各种可用选项。技术选型的目标是确定与项目目标相符合、能够有效解…...

计算机视觉的多模态模型

计算机视觉的多模态模型 是指能够同时处理和理解 多种类型数据&#xff08;模态&#xff09; 的模型。这些模态可以包括图像、文本、音频、视频、深度信息等。多模态模型的核心目标是利用不同模态之间的互补信息&#xff0c;提升模型的性能和泛化能力。 1. 多模态模型的核心思想…...

Scrapy 入门教程

Scrapy 入门教程 Scrapy 是一个用于爬取网站数据的 Python 框架&#xff0c;功能强大且易于扩展。本文将介绍 Scrapy 的基本概念、安装方法、使用示例&#xff0c;并展示如何编写一个基本的爬虫。 1. 什么是 Scrapy&#xff1f; Scrapy 是一个开源的、用于爬取网站数据的框架…...

Oracle OCP认证是否值得考?

Oracle OCP&#xff08;Oracle Certified Professional&#xff09;认证是数据库领域的传统权威认证&#xff0c;但随着云数据库和开源技术的崛起&#xff0c;其价值正面临分化。是否值得考取&#xff0c;需结合你的职业定位、行业需求及长期规划综合判断。以下是关键分析&…...

OpenCV中距离公式

一、各类距离公式总结 常见距离公式 欧氏距离&#xff1a; 曼哈顿距离&#xff08;L1&#xff09;‌&#xff1a; 切比雪夫距离&#xff08;Chessboard&#xff09;‌&#xff1a; 1、点与点距离(欧氏距离) ‌二维空间‌ 设两点坐标为 P1(x1,y1)、P2(x2,y2)&#xff0c;其距离…...

DeepSeek自学手册:《从理论(模型训练)到实践(模型应用)》|73页|附PPT下载方法

导 读INTRODUCTION 今天分享是由ai呀蔡蔡团队带来的DeepSeek自学手册&#xff1a;《从理论&#xff08;模型训练&#xff09;到实践&#xff08;模型应用&#xff09;》&#xff0c;这是一篇关于DeepSeek模型训练、应用场景及替代方案的综合指南文章&#xff0c;主要介绍了Deep…...

Doris官网上没有的一些Fe参数了,都在源码中

一、FE配置源码 apache-doris-src\fe\fe-common\src\main\java\org\apache\doris\common\Config.java 二、BE配置源码 apache-doris-src\be\src\common\config.cpp 三、FE源码 package org.apache.doris.common;public class Config extends ConfigBase {ConfField(descri…...

(一)丶Windows安装RabbitMQ可能会遇到的问题

一丶可能会忘了配置ERLang的环境变量 二丶执行命令时报错 第一步 rabbitmq-plugins enable rabbitmq_management 第二部 rabbitmqctl status 三丶修改.erlang.cookie 文件 1.找到C盘目下的.erlang.cookie文件 C:\Users\admin\.erlang.cookie C:\Windows\System32\config\sys…...

stm32g030移植RT-Thread

移植流程 移植前需要安装Keil.STM32G0xx_DFP.1.2.0.pack组件&#xff0c;大致的移植过程&#xff1a; CubeMX配置RT-Thread组件配置工程模板配置 参考例程配置&#xff1a;拷贝仓库原有的stm32g070-st-nucleo工程&#xff0c;然后另起一个名字&#xff0c;目录结构如下 完整…...

Parsing error: Unexpected token, expected “,“

今天在使用Trae AI 编程工具开发大文件切片上传功能&#xff0c;使用的是VUE3,TS技术栈&#xff0c;开发完成运行时&#xff0c;编译报错&#xff08;Parsing error: Unexpected token, expected ","&#xff09;&#xff0c;让AI自行修复此问题多次后还是没有解决&a…...

Day23: 数组中数字出现的次数

整数数组 sockets 记录了一个袜子礼盒的颜色分布情况&#xff0c;其中 sockets[i] 表示该袜子的颜色编号。礼盒中除了一款撞色搭配的袜子&#xff0c;每种颜色的袜子均有两只。请设计一个程序&#xff0c;在时间复杂度 O(n)&#xff0c;空间复杂度O(1) 内找到这双撞色搭配袜子的…...