windows编程之线程同步万字总结(创建线程,互斥对象,互斥事件,信号量,关键段,多线程群聊服务器)
文章目录
- 创建线程
- 方法一_beginthreadex
- 函数讲解
- 使用示例:
- 方法二CreateThread
- 函数讲解:
- 使用示例:
- 互斥对象:
- 创建互斥对象CreateMutex
- 互斥事件
- 介绍
- 创建或打开一个未命名的互斥事件对象
- 信号量
- 介绍
- 信号量的相关函数
- 使用示例
- 关键段
- 相关函数
- 错误使用示例
- 正确使用示例
- 综合demo多线程群聊服务器(带客户端)
- 服务端代码
- 客户端代码
- 效果展示
创建线程
window创建线程的方法有两种
方法一_beginthreadex
函数讲解
所在头文件:
#include<process.h>函数原型:
uintptr_t _beginthreadex( void *security,//指向 SECURITY_ATTRIBUTES 结构的指针,此结构确定返回的句柄是否由子进程继承。 如果 Security 为 NULL,则无法继承句柄。unsigned stack_size,//新线程的堆栈大小或 0。unsigned ( __clrcall *start_address )( void * ),//启动开始执行新线程的例程的地址。(也就是线程函数的地址)void *arglist,/要传递到新线程的参数列表或 NULL。unsigned initflag,//控制新线程的初始状态的标志。 将 initflag 设置为 0 以立即运行,或设置为 CREATE_SUSPENDED 以在挂起状态下创建线程unsigned *thrdaddr//指向接收线程标识符的 32 位变量。 如果是 NULL,则不使用它。
);线程函数示例:
unsigned WINAPI 函数名称(void*arg)
{
//参数不能掉.........return 0;
}//获取创建的线程的句柄
HANDLE threadHandle=(HANDLE)_beginthreadex(.....);//_beginthreadex()创建的线程会自动关闭线程句柄
使用示例:
#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
#include<process.h>
unsigned WINAPI thread_01(void*arg)
{int cnt = *((int*)arg);for (int i = 0; i < cnt; i++){printf_s("thread_01\n");Sleep(1000);}return 0;
}unsigned WINAPI thread_02(void* arg)
{int cnt = *((int*)arg);for (int i = 0; i < cnt; i++){printf_s("thread_02\n");Sleep(1000);}return 0;}unsigned WINAPI thread_03(void* arg)
{int cnt = *((int*)arg);for (int i = 0; i < cnt; i++){printf_s("thread_03\n");Sleep(1000);}return 0;}int main()
{int a = 10, b = 7, c = 4;HANDLE thread1=(HANDLE)_beginthreadex(NULL,0,&thread_01,(void*)&a,0,NULL);HANDLE thread2=(HANDLE)_beginthreadex(NULL, 0, &thread_02, (void*)&b, 0, NULL);HANDLE thread3=(HANDLE)_beginthreadex(NULL, 0, &thread_03, (void*)&c, 0, NULL);WaitForSingleObject(thread1);//主线程等待thread1执行完 WaitForSingleObject(thread2);WaitForSingleObject(thread3);//这里要注意WaitForSingleObject(thread1);并不会阻塞 WaitForSingleObject(thread2);//WaitForSingleObject(thread3);这两句的执行system("pause");return 0;
}
演示效果:
方法二CreateThread
函数讲解:
//函数原型
HANDLE CreateThread([in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定是否可由子进程继承返回的句柄。 如果 lpThreadAttributes 为 NULL,则无法继承句柄。[in] SIZE_T dwStackSize,//堆栈的初始大小(以字节为单位)。 系统将此值舍入到最近的页面。 如果此参数为零,则新线程将使用可执行文件的默认大小。 [in] LPTHREAD_START_ROUTINE lpStartAddress,//指向要由线程执行的应用程序定义函数的指针。 此指针表示线程的起始地址。[in, optional] __drv_aliasesMem LPVOID lpParameter,//指向要传递给线程的变量的指针。[in] DWORD dwCreationFlags,//控制线程创建的标志。[out, optional] LPDWORD lpThreadId//指向接收线程标识符的变量的指针。 如果此参数为 NULL,则不返回线程标识符。
);
//参数dwCreationFlags的可能取值
使用示例:
#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
#include<process.h>
unsigned WINAPI thread_01(void*arg)
{int cnt = *((int*)arg);for (int i = 0; i < cnt; i++){printf_s("thread_01\n");Sleep(1000);}return 0;
}unsigned WINAPI thread_02(void* arg)
{int cnt = *((int*)arg);for (int i = 0; i < cnt; i++){printf_s("thread_02\n");Sleep(1000);}return 0;}unsigned WINAPI thread_03(void* arg)
{int cnt = *((int*)arg);for (int i = 0; i < cnt; i++){printf_s("thread_03\n");Sleep(1000);}return 0;}DWORD WINAPI thread_04 (LPVOID p)
{int cnt = *((int*)p);for (int i = 0; i < cnt; i++){printf_s("我是线程%d\n", GetCurrentThreadId());Sleep(1000);}return 0;
}int main()
{int a = 10, b = 7, c = 4,d=8;//方法一HANDLE thread1=(HANDLE)_beginthreadex(NULL,0,&thread_01,(void*)&a,0,NULL);HANDLE thread2=(HANDLE)_beginthreadex(NULL, 0, &thread_02, (void*)&b, 0, NULL);HANDLE thread3=(HANDLE)_beginthreadex(NULL, 0, &thread_03, (void*)&c, 0, NULL);/*当使用 _beginthreadex 创建线程时,它返回一个线程句柄(HANDLE),该句柄可以用于对线程进行操作和控制。这个句柄是由 _beginthreadex 内部创建并分配的。在线程运行结束后,系统会自动关闭线程句柄,并释放与之相关的资源。*///方法二HANDLE thread4 = CreateThread(NULL,0,thread_04,(void*)&d,0,NULL);CloseHandle(thread4);//关闭线程的句柄释放相关的操作系统资源WaitForSingleObject(thread1,INFINITE);//主线程等待thread1执行完 WaitForSingleObject(thread2,INFINITE);WaitForSingleObject(thread3,INFINITE);WaitForSingleObject(thread4,INFINITE);system("pause");return 0;
}
演示效果:
互斥对象:
ps:你也可以理解为互斥锁
创建互斥对象CreateMutex
HANDLE CreateMutex([in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针。 如果此参数为 NULL,则子进程无法继承句柄。[in] BOOL bInitialOwner,//如果此值为 TRUE 并且调用方创建了互斥体,则调用线程获取互斥对象的初始所有权。 否则,调用线程不会获得互斥体的所有权。 若要确定调用方是否创建了互斥体,请参阅返回值部分。[in, optional] LPCWSTR lpName//互斥对象的名称。 名称限制为 MAX_PATH 个字符。 名称比较区分大小写。
);
使用示例:
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<process.h>
int num = 50000;
HANDLE hMutex;
unsigned WINAPI thread_01(void* arg)
{WaitForSingleObject(hMutex,INFINITE);//阻塞直到拿到锁for (int i = 0; i < 5000; i++){--num;}ReleaseMutex(hMutex);//释放锁return 0;
}unsigned WINAPI thread_02(void* arg)
{WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < 5000; i++){++num;}return 0;
}
int main()
{HANDLE tHandles[20];hMutex = CreateMutex(NULL, FALSE, NULL);//创建互斥对象for (int i = 0; i < 20; i++){if (i % 2 == 0)tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, thread_01, NULL, 0, NULL);elsetHandles[i] = (HANDLE)_beginthreadex(NULL, 0, thread_02, NULL, 0, NULL);}WaitForMultipleObjects(20, tHandles, TRUE, INFINITE);CloseHandle(hMutex);printf("num=%d\n", num);//50000system("pause");return 0;
}
效果演示:
互斥事件
介绍
Windows中的互斥事件对象是一种同步对象,用于多个线程之间的互斥访问共享资源。它被用作一种机制,确保在任意时刻只有一个线程能够访问被保护的资源。
互斥事件对象有两个状态:有信号(signaled)和无信号(nonsignaled)。当互斥事件对象处于有信号状态时,表示资源可供访问,其他线程可以继续执行;当互斥事件对象处于无信号状态时,表示资源正在被其他线程使用,当前线程需要等待。
以下是使用互斥事件对象的一般流程:
1、创建互斥事件对象:使用CreateEvent函数创建一个互斥事件对象,并指定其初始状态。
2、等待互斥事件对象:使用WaitForSingleObject或WaitForMultipleObjects函数等待互斥事件对象。如果互斥事件对象处于无信号状态,当前线程会被阻塞,直到互斥事件对象变为有信号状态。
3、访问共享资源:互斥事件对象变为有信号状态后,当前线程可以访问共享资源。在访问完毕后,需要释放互斥事件对象。
4、释放互斥事件对象:使用SetEvent函数将互斥事件对象设为有信号状态,以允许其他线程访问共享资源。如果没有其他线程在等待互斥事件对象,它将保持有信号状态。
互斥事件对象的使用可以有效避免多个线程同时访问共享资源而导致的数据竞争和不一致性问题。
创建或打开一个未命名的互斥事件对象
HANDLE CreateEventA([in, optional] LPSECURITY_ATTRIBUTES lpEventAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针。 如果此参数为 NULL,则子进程无法继承句柄。 安全描述符[in] BOOL bManualReset,//如果此参数为 TRUE,则函数将创建手动重置事件对象,该对象需要使用 ResetEvent 函数将事件状态设置为非签名。 如果此参数为 FALSE,则函数将创建一个自动重置事件对象,在释放单个等待线程后,系统会自动将事件状态重置为未签名。[in] BOOL bInitialState,//如果此参数为 TRUE,则会向事件对象发出初始状态信号;否则,它将不进行签名。[in, optional] LPCSTR lpName//事件对象的名称。 名称限制为 MAX_PATH 个字符。 名称比较区分大小写。
);/*
如果函数成功,则返回值是事件对象的句柄。
如果命名事件对象在函数调用之前存在,则函数将返回现有对象的句柄,
GetLastError 将返回 ERROR_ALREADY_EXISTS。
如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError。
*/SetEvent(Handle event);//将事件对象event设置为有信号状态,这相当于还没线程拿到这把锁
ResetEvent(Handle event);//将事件对象重置为无信号状态
信号量
介绍
触发状态(有信号状态),表示有可用资源。
未触发状态(无信号状态),表示没有可用资源
信号量的组成
①计数器:该内核对象被使用的次数
②最大资源数量:标识信号量可以控制的最大资源数量(带符号的 32 位)
③当前资源数量:标识当前可用资源的数量(带符号的 32 位)。即表示当前
开放资源的个数(注意不是剩下资源的个数),只有开放的资源才能被线程所申
请。但这些开放的资源不一定被线程占用完。比如,当前开放 5 个资源,而只有
3 个线程申请,则还有 2 个资源可被申请,但如果这时总共是 7 个线程要使用信
号量,显然开放的资源 5 个是不够的。这时还可以再开放 2 个,直到达到最大资
源数量。
信号量的相关函数
//创建信号量
HANDLE WINAPI CreateSemaphoreW(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // Null 安全属性
_In_ LONG lInitialCount, //初始化时,共有多少个资源是可以用的。 0:未触发状//态(无信号
状态),表示没有可用资源
_In_ LONG lMaximumCount, //能够处理的最大的资源数量 3
_In_opt_ LPCWSTR lpName //NULL 信号量的名称
);//增加信号量WINAPI ReleaseSemaphore(
_In_ HANDLE hSemaphore, //信号量的句柄
_In_ LONG lReleaseCount, //将lReleaseCount值加到信号量的当前资源计数上面 0-> 1
_Out_opt_ LPLONG lpPreviousCount //当前资源计数的原始值
);WaitForSingleObject(信号量对象的句柄, INFINITE)
当这个函数检测到信号量对象的资源计数大于0的时候会立即返回同时该信号量对象的资源计数值减一
使用示例
#include <stdio.h>
#include <windows.h>
#include <process.h>unsigned WINAPI Read(void* arg);
unsigned WINAPI Accu(void* arg);
static HANDLE semOne;
static HANDLE semTwo;
static int num;int main(int argc, char* argv[])
{
HANDLE hThread1, hThread2;
semOne = CreateSemaphore(NULL, 0, 1, NULL);
//semOne 没有可用资源 只能表示0或者1的二进制信号量 无信号
semTwo = CreateSemaphore(NULL, 1, 1, NULL);
//semTwo 有可用资源,有信号状态 有信号
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(semOne);
CloseHandle(semTwo);
system("pause");
return 0;
}unsigned WINAPI Read(void* arg)
{
int i;
for (i = 0; i < 5; i++)
{
fputs("Input num: ", stdout); // 1 5 11
printf("begin read\n"); // 3 6 12
//等待内核对象semTwo的信号,如果有信号,继续执行;如果没有信号,等待
WaitForSingleObject(semTwo, INFINITE);
printf("beginning read\n"); //4 10 16
scanf("%d", &num);
ReleaseSemaphore(semOne, 1, NULL);
}
return 0;
}unsigned WINAPI Accu(void* arg)
{
int sum = 0, i;
for (i = 0; i < 5; i++)
{
printf("begin Accu\n"); //2 9 15
//等待内核对象semOne的信号,如果有信号,继续执行;如果没有信号,等待
WaitForSingleObject(semOne, INFINITE);
printf("beginning Accu\n"); //7 13
sum += num;
printf("sum = %d \n", sum); // 8 14
ReleaseSemaphore(semTwo, 1, NULL);
}
printf("Result: %d \n", sum);
return 0;
}
关键段
这个和互斥事件,互斥对象差不多
相关函数
初始化:
InitializeCriticalSection(CRITICAL_SECTION *cs);进入关键段:
EnterCriticalSection(&cs);离开关键段:
LeaveCriticalSection(&cs);删除关键段:
DeleteCriticalSection(&cs);//删除临界区进入关键段之后到离开关键段之前的所有资源都被保证同一时刻只允许一个线程访问
错误使用示例
#include<process.h>
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>CRITICAL_SECTION cs;
static int ticketsNum = 200;
unsigned WINAPI thread_01(void*arg)
{
;//开始卖票while (ticketsNum > 0){//进入关键段EnterCriticalSection(&cs);printf_s("窗口一卖出一张票,还剩%d张票\n", --ticketsNum);//离开关键段LeaveCriticalSection(&cs);}return 0;
}unsigned WINAPI thread_02(void*Arg)
{;//开始卖票while (ticketsNum > 0){//进入关键段EnterCriticalSection(&cs);printf_s("窗口二卖出一张票,还剩%d张票\n", --ticketsNum);//离开关键段LeaveCriticalSection(&cs);}return 0;
}int main()
{HANDLE thread1, thread2;InitializeCriticalSection(&cs);thread1 = (HANDLE)_beginthreadex(NULL, 0, thread_01, NULL, 0, NULL);thread2 = (HANDLE)_beginthreadex(NULL, 0, thread_02, NULL, 0, NULL);WaitForSingleObject(thread1,INFINITE);WaitForSingleObject(thread2, INFINITE);CloseHandle(thread1);CloseHandle(thread2);DeleteCriticalSection(&cs);//删除临界区printf_s("票卖完了\n");system("pause");return 0;
}
错误效果演示:
为什么会多卖出一张票呢,因为这里对于循环条件ticketsNum > 0判断的时候还没有进入关键段
所以这里两个线程可能同时判断了然后都进入了循环
正确使用示例
#include<process.h>
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>CRITICAL_SECTION cs;
static int ticketsNum = 200;
unsigned WINAPI thread_01(void* arg)
{;//开始卖票for (;;){//进入关键段EnterCriticalSection(&cs);if (ticketsNum <= 0)break;printf_s("窗口一卖出一张票,还剩%d张票\n", --ticketsNum);//离开关键段LeaveCriticalSection(&cs);}return 0;
}unsigned WINAPI thread_02(void* Arg)
{;//开始卖票for (;;){//进入关键段EnterCriticalSection(&cs);if (ticketsNum <= 0)break;printf_s("窗口二卖出一张票,还剩%d张票\n", --ticketsNum);//离开关键段LeaveCriticalSection(&cs);}return 0;
}int main()
{HANDLE thread1, thread2;InitializeCriticalSection(&cs);thread1 = (HANDLE)_beginthreadex(NULL, 0, thread_01, NULL, 0, NULL);thread2 = (HANDLE)_beginthreadex(NULL, 0, thread_02, NULL, 0, NULL);WaitForSingleObject(thread1, INFINITE);WaitForSingleObject(thread2, INFINITE);CloseHandle(thread1);CloseHandle(thread2);DeleteCriticalSection(&cs);//删除临界区printf_s("票卖完了\n");system("pause");return 0;
}
效果演示:
综合demo多线程群聊服务器(带客户端)
服务端代码
#include<stdio.h>
#include<windows.h>
#include<process.h>
#pragma comment(lib,"ws2_32.lib") //库文件#define MAX_CLIENT 256//最大连接数
#define MAX_BUF_SIZE 1024//发送数据最大大小
SOCKET clients[MAX_CLIENT];//客户端连接队列
int clientNum = 0;//客户端连接数
HANDLE hMutex;//互斥对象void SendMsg(char* msg,int iLen)
{WaitForSingleObject(hMutex, INFINITE);//转发消息for (int i = 0; i < clientNum; i++){send(clients[i], msg, iLen, 0);}ReleaseMutex(hMutex);
}
unsigned WINAPI HandleCln(void* arg)
{//线程处理函数SOCKET Client = *((SOCKET*)arg);int iLen = 0;char szMsg[MAX_BUF_SIZE] = { 0 };for(;;){iLen = recv(Client, szMsg, sizeof(szMsg), 0);if (iLen != -1){SendMsg(szMsg, iLen);}else{break;}}//处理下线过程WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < clientNum; i++){if (clients[i] == Client){while (i++ < clientNum){clients[i] = clients[i + 1];}clientNum--;printf_s("当前连接客户端数:%d\n",clientNum);break;}}ReleaseMutex(hMutex);return 0;
}
int main()
{/*******************初始化服务器*******************///初始化网络库WORD wVersionRequested;WSADATA wsaData;int err;wVersionRequested = MAKEWORD(2, 2);err = WSAStartup(wVersionRequested, &wsaData);if (err != 0){printf_s("WSAStartup errorNum = % d\n", GetLastError());}if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf_s("LOBYTE errorNum=%d\n", GetLastError());WSACleanup();}//创建服务器套接字SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(6000);//绑定if (SOCKET_ERROR == bind(serverSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){printf_s("bind error=%d\n", GetLastError());return -1;}//创建互斥对象hMutex = CreateMutex(NULL, FALSE, NULL);//监听if (SOCKET_ERROR == listen(serverSocket, MAX_CLIENT))//最大连接/监听数为5{printf_s("listen error=%d\n", GetLastError());return -1;}SOCKADDR_IN addrClient;int len = sizeof(SOCKADDR_IN);HANDLE hThread;for(;;){//接收客户端连接SOCKET sockConnect = accept(serverSocket, (SOCKADDR*)&addrClient, &len);//放入队列WaitForSingleObject(hMutex, INFINITE);clients[clientNum++] = sockConnect;ReleaseMutex(hMutex);hThread = (HANDLE)_beginthreadex(NULL,0,&HandleCln,(void*)&sockConnect,0,NULL);printf_s("connect client IP=%s clientNum=%d\n",inet_ntoa(addrClient.sin_addr),clientNum);}WSACleanup();return 0;
}
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<process.h>
#pragma comment(lib,"ws2_32.lib") //库文件
#define MAX_SIZE 1024 //收发消息的最大长度
#define MAX_NAME_SIZE 32//昵称最大长度 HANDLE hSend;//发送线程
HANDLE hAceept;//接收线程
char nickName[MAX_NAME_SIZE];//昵称unsigned WINAPI sendMsg(void* arg)
{//发送消息给服务端SOCKET ClientSocket = *((SOCKET*)arg);char msg[MAX_SIZE] = { 0 };char name_msg[MAX_SIZE + MAX_NAME_SIZE] = { 0 };for (;;){memset(msg, 0, MAX_SIZE);//阻塞在这一句直到接收到控制台输入fgets(msg, MAX_SIZE, stdin);if (!strcmp(msg, "Q\n") || !strcmp(msg, "q\n")){//客户端口下线closesocket(ClientSocket);exit(0);}//将消息拼包发送给服务器sprintf(name_msg, "%s :%s", nickName,msg);send(ClientSocket, name_msg, strlen(name_msg), 0);}return 0;
}unsigned WINAPI acceptMsg(void* arg)
{//接收服务端的消息SOCKET ClientSocket = *((SOCKET*)arg);char name_msg[MAX_SIZE + MAX_NAME_SIZE] = { 0 };int iLen = 0;for (;;){memset(name_msg, 0, MAX_NAME_SIZE + MAX_SIZE);iLen = recv(ClientSocket, name_msg, sizeof(name_msg),0);if (iLen == -1){return 2;}name_msg[iLen] = 0;//将接收到的消息输出到控制台fputs(name_msg, stdout);}return 0;
}
int main()
{//初始化网络库WORD wVersionRequested;WSADATA wsaData;int err;wVersionRequested = MAKEWORD(2, 2);err = WSAStartup(wVersionRequested, &wsaData);if (err != 0){printf_s("WSAStartup errorNum = % d\n", GetLastError());}if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf_s("LOBYTE errorNum=%d\n", GetLastError());WSACleanup();}//安装电话机SOCKET ClientSocket = socket(AF_INET, SOCK_STREAM, 0);if (INVALID_SOCKET == ClientSocket){printf_s("socket error=%d\n", GetLastError());return -1;}//配置要连接的服务器SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(6000);//连接服务器if (SOCKET_ERROR == connect(ClientSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){printf_s("connect error=%d\n", GetLastError());return -1;}//输入昵称printf_s("请输入您的昵称:");scanf("%s", nickName);//清空输入缓冲区while (getchar() != '\n') {continue;}//开启发送接收线程hSend = (HANDLE)_beginthreadex(NULL, 0, &sendMsg, (void*)&ClientSocket, 0, NULL);hAceept = (HANDLE)_beginthreadex(NULL, 0, &acceptMsg, (void*)&ClientSocket, 0, NULL);//主线程等待WaitForSingleObject(hSend, INFINITE);WaitForSingleObject(hAceept, INFINITE);WSACleanup();printf_s("退出");return 0;
}
效果展示
相关文章:
windows编程之线程同步万字总结(创建线程,互斥对象,互斥事件,信号量,关键段,多线程群聊服务器)
文章目录 创建线程方法一_beginthreadex函数讲解使用示例: 方法二CreateThread函数讲解:使用示例: 互斥对象:创建互斥对象CreateMutex 互斥事件介绍创建或打开一个未命名的互斥事件对象 信号量介绍信号量的相关函数使用示例 关键段相关函数错误使用示例正确使用示例…...
Git在已有的项目中引入Submodule子模块管理:添加、更新、删除(实战示例代码)
前言 在进行Git版本控制的过程中,有时候我们需要在已有的项目中引入子模块,以便复用其他独立的Git存储库的代码或文件。本文将详细介绍如何在已有项目下添加、更新和删除Git的Submodule子模块,并提供相关的示例代码。 实战场景 假设我们已…...
内网穿透实现Windows远程桌面访问Ubuntu,简单高效的远程桌面解决方案
文章目录 前言1. ubuntu安装XRDP2.局域网测试连接3.安装cpolar内网穿透4.cpolar公网地址测试访问5.固定域名公网地址 前言 XRDP是一种开源工具,它允许用户通过Windows RDP访问Linux远程桌面。 除了Windows RDP外,xrdp工具还接受来自其他RDP客户端(如Fre…...
如何学习运营管理
运营管理(Operations Management)是一门管理学科,它关注如何高效地组织和管理企业的生产、服务、供应链和业务过程以达到组织的目标。运营管理是企业管理的一个重要领域,它包含了多个内容和职能: 生产管理:…...
腾讯云centos7.6安装部署备忘
1.Mysql 1.1 安装mysql wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum install mysql-community-server 1.1.1 安装后重启 service mysqld restart 1.1.2 初次安装mysql,root账…...
【赠书活动】考研备考书单推荐
👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化【获取源码商业合作】 👉荣__誉👈:阿里云博客专家博主、5…...
中缀表达式 - 栈实现综合计算器
代码: package Algotithm.stackobject Calculator {def main(args: Array[String]): Unit {val expression "32*6-2"//创建两个栈:数栈、符号栈val numStack, operStack new ArrayStack2(10)//定义需要的相关变量var index, num1, num2, …...
html语音播报功能问题
语音播报有个问题,就是弹出层有时无法关闭页面的播报,如果弹出层也有语音播报,就会造成语音混者播放 解决办法就是在弹出窗口(我用的弹出层框架是layui的)之前清空语音 window.operEdit function (url, title){window.speechSynthesis.can…...
计算机重点学科评级B-,山东省属重点高校考情分析
山东科技大学(B-) 考研难度(☆☆) 内容:23考情概况(拟录取和复试分析)、院校概况、23专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文1175字预计阅读:3分钟 2023考情概况 山东科技大学计…...
轻松搭建本地知识库的ChatGLM2-6B
近期发现了一个项目,它的前身是ChatGLM,在我之前的博客中有关于ChatGLM的部署过程,本项目在前者基础上进行了优化,可以基于当前主流的LLM模型和庞大的知识库,实现本地部署自己的ChatGPT,并可结合自己的知识…...
flink的物理DataFlow图及Slot处理槽任务分配
背景 在flink中,有几个比较重要的概念,逻辑DataFlow图,物理DataFlow图以及处理槽执行任务,本文就来讲解下这几个概念 概念详解 假设有以下代码:数据源和统计单词算子的并行度是2,数据汇算子的并行度是1&…...
与面试相关的redis
这里写自定义目录标题 📝 redis的知识点数据结构及其特性,用途和操作方法持久化高可用分布式锁发布订阅性能优化安全性数据分片缓存策略键过期删除策略内存淘汰策略 🤗 总结归纳📎 参考文章 😀 这里写文章的前言&#…...
MapStruct从0到0.5
MapStruct从0到0.5 开发的过程,经常会用到实体类属性映射,同时为了方便,开发者也很少自己写专门的属性赋值工具类。索性会直接使用Sprrng提供的BeanUtils工具类,然后在性能上和字段属性赋值上的问题,一直是为开发者所…...
STM32H750 HAL CUBEMX 时钟失败及死机无法下载问题解决
芯片采样电压设置,否则 无法运行 解决死机问题 设置swd 模式 短接 boot0 —vcc 3.3v即可正常下载...
paddlespeech on centos7
概述 paddlespeech是百度飞桨平台的开源工具包,主要用于语音和音频的分析处理,其中包含多个可选模型,提供语音识别、语音合成、说话人验证、关键词识别、音频分类和语音翻译等功能。 paddlespeech整体是比较简单易用的,但是安装…...
ROM是什么? 刷ROM是什么意思?
文章目录 ROM是什么?刷ROM是什么意思 ROM是什么? ROM是只读内存(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除。通常用在不需经常变更资料的…...
华为云Stack的学习(五)
六、华为云stack服务简介 1.云服务在华为云Stack中的位置 云服务对接多个数据中心资源池层提供的资源,并向各种行业应用提供载体。 2.华为云Stack通用服务 2.1 云计算的服务模式 2.2 计算相关的云服务 2.3 存储相关的云服务 2.4 网络相关的云服务 3.云化案例 **…...
【LeetCode-中等题】904. 水果成篮
文章目录 题目方法一:滑动窗口方法二: 题目 题目的意思就是:找至多包含两种元素的最长子串,返回其长度 方法一:滑动窗口 class Solution { // 滑动窗口 找至多包含两种元素的最长子串,返回其长度public …...
【C++】哈希——哈希的概念,应用以及闭散列和哈希桶的模拟实现
前言: 前面我们一同学习了二叉搜索树,以及特殊版本的平衡二叉搜索树,这些容器让我们查找数据的效率提高到了O(log^2 N)。虽然效率提高了很多,但是有没有一种理想的方法使得我们能提高到O(1)呢?其实在C语言数据结构中&a…...
Kubernetes (K8s) 解读:微服务与容器编排的未来
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🐅🐾猫头虎建议程序员必备技术栈一览表📖: 🛠️ 全栈技术 Full Stack: 📚…...
JavaScript学习--Day04
元字符 边界符: /^/:以什么开头 /$/:以什么结尾 量词: 预定义类:...
HCS 基本概念(三)
一、定义 HCS采用FusionSphere OpenStack作为云平台,对各个物理数据中心资源做整合,采用ManageOne作为数据中心管理软件对多个数据中心提供统一管理,通过云平台和数据中心管理软件协同运作,达到多数据中心融合、提升企业整体IT效率…...
通过curl命令分析http接口请求各阶段的耗时等
目录 一、介绍二、功能1、-v 输出请求 响应头状态码 响应文本等信息2、-x 测试代理ip是否能在该网站使用3、-w 额外输出查看接口请求响应的消耗时间4、-o 将响应结果存储到文件里面5、-X post请求测试 (没测成功用的不多) 一、介绍 Curl是一个用于发送和接收请求的命令行工具和…...
Linux工具——gcc
目录 一,gcc简介 二,C语言源文件的编译过程 1.预处理 2.编译 3.汇编 4.链接 5.动静态库 一,gcc简介 相信有不少的小白和我一样在学习Linux之前只听说过visual studio。其实这个gcc这个编译器实现的功能便是和visual studio一样的功能&…...
uni-app 使用uCharts-进行图表展示(折线图带单位)
前言 在uni-app经常是需要进行数据展示,针对这个情况也是有人开发好了第三方包,来兼容不同平台展示 uCharts和pc端的Echarts使用差不多,甚至会感觉在uni-app使用uCharts更轻便,更舒服 但是这个第三方包有优点就会有缺点…...
180B参数的Falcon登顶Hugging Face,vs chatGPT 最好开源大模型使用体验
文章目录 使用地址使用体验test1:简单喜好类问题test2:知识性问题test3:开放性问题test4:中文支持test5:问题时效性test6:学术问题使用地址 https://huggingface.co/spaces/tiiuae/falcon-180b-demo 使用体验 相比Falcon-7b,Falcon-180b拥有1800亿的参数量...
服务器数据恢复-EMC存储磁盘损坏的RAID5数据恢复案例
服务器数据恢复环境: 北京某单位有一台EMC某型号存储,有一组由10块STAT硬盘组建的RAID5阵列,另外2块磁盘作为热备盘使用。RAID5阵列上层只划分了一个LUN,分配给SUN小机使用,上层文件系统为ZFS。 服务器故障࿱…...
Nginx优化文件上传大小限制
Nginx默认配置 Nginx 默认情况下,上传文件的大小为1M,超过1M就会返回413错误。只用对Nginx进行简单配置即可解决问题。 优化Nginx文件上传大小限制 可以在Nginx配置文件中配置 client_max_body_size 配置项。 设置客户端请求正文允许的最大大小。如果…...
navicat SSH连接数据库报错: Putty key format too new
问题 下载 Putty 0.79 生成了密钥,但是在navicat 15 使用SSH通道连接数据库报错: Putty key format too new 错误原因和处理 原来是因为生成的私钥格式是 V3 , navicat 15 只能识别 V2 所以,在 PuTTYgen Load 私钥,重新保存为 …...
基于大规模MIMO通信系统的半盲信道估计算法matlab性能仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 %EM算法收敛所需的迭代 nIter 1; Yp Y(:,1:L_polit,:); %与导频序列相对应的部分 q…...
汉南网站建设/最新中国新闻
class Solution { public:vector<int> decode(vector<int>& encoded, int first) {int n encoded.size();vector<int> res(n 1);res[0] first;for(int i 1; i < n; i) {res[i] encoded[i - 1] ^ res[i - 1];}return res;} };...
手机自己做网站吗/查网站权重
Python 3.x与Python 2.x之间存在着较多的语法细节差异。今天在看Python核心编程的时候,说到了自定义迭代器对象。于是动手将源码打了一遍,原书代码如下: class AnyIter(object):def __init__(self, data, safeFalse):""" The …...
网站开发什么语言安全/快速网站排名提升
现代社会科学和信息技术的发展,特别是互联网技术和云计算的发展,有两个特点:一是数据量大,活动量大;二是业务数据从非常枯燥的数据到海量的数据,结构和非结构数据、复杂数据、如何随着时间的推移收集和管理数据、研究数据和可视化以使其更高效和直观地帮助管理者做出最佳决策。…...
一个人做网站现实吗/网络营销软件站
原文: http://coolshell.cn/articles/11564.html TCP是一个巨复杂的协议,因为他要解决很多问题,而这些问题又带出了很多子问题和阴暗面。所以学习TCP本身是个比较痛苦的过程,但对于学习的过程却能让人有很多收获。关于TCP这个协议的细节&…...
新浪微博做wordpress图床/上海自媒体推广
要想把日语翻译做好,既要学会捕捉关键词句,有良好地逻辑思维,又要具备一定的翻译技巧。下面小编给大家介绍一些常用的日语翻译技巧,欢迎阅读学习!常用的日语翻译技巧一、反译日语句子中经常喜欢用双重否定来表示肯定,这…...
网站开发一般多钱/天津seo推广软件
All objects on the heap contain two overhead members: the type object pointer and the sync block index.转载于:https://www.cnblogs.com/zhtf2014/archive/2011/07/29/2121594.html...