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,/要传递到新线程的参数列表或 NULLunsigned 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;
}

效果展示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/125086.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android12之解析/proc/pid进程参数(一百六十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Streamlit项目:乐高风格马赛克设计工坊~打造个性化马赛克图案的平台

文章目录 1 前言2 项目概述2.1 项目背景和目标2.2 功能和特性2.3 技术实现2.4 开发计划2.5 预期成果2.6 应用场景 3 使用指南3.1 源代码使用指南3.2 普通网页用户使用指南3.3 文件保存3.3.1 导出图像文件3.3.2 获取像素RGB数据 3.4 注意事项 4 实现细节4.1 准备工作4.2 编写代码…

你为什么总招不到对的人?

办公室里&#xff0c;技术面试官Arron 和 HR 正对近期的招聘失误进行争执——新招的技术岗试用了几个月&#xff0c;就因能力不足离开了。 HR 不断吐槽岗位画像太模糊&#xff0c;Arron 反问&#xff1a;“不是给了你关键词吗&#xff1f;” HR 气不打一处来&#xff1a;“你…

Spring IOC的理解

总&#xff1a; 控制反转&#xff08;IOC&#xff09;&#xff1a;理论思想&#xff0c;传统java开发模式&#xff0c;对象是由使用者来进行管理&#xff0c;有了spring后&#xff0c;可以交给spring来帮我们进行管理。依赖注入&#xff08;DI&#xff09;&#xff1a;把对应的…

[构建 Vue 组件库] 小尾巴 UI 组件库 —— 横向商品卡片(仿淘宝)

文章归档于&#xff1a;https://www.yuque.com/u27599042/row3c6 组件库地址 npm&#xff1a;https://www.npmjs.com/package/xwb-ui?activeTabreadmegitee&#xff1a;https://gitee.com/tongchaowei/xwb-ui 下载 npm i xwb-ui配置 按需导入 import {组件名 } from xwb-…

解耦只是一个巧合?

本文分享一篇在IJCAI2023看到的文章&#xff1a;Overlooked Implications of the Reconstruction Loss for VAE Disentanglement 首先回顾下VAE&#xff0c;其loss函数有两项&#xff0c;一项是重构误差&#xff0c;另一项是正则项&#xff1a; L r e c ( x , x ^ ) E q ϕ (…

Unity——脚本与导航系统

Unity内置了一个比较完善的导航系统&#xff0c;一般称为Nav Mesh&#xff08;导航网格&#xff09;&#xff0c;用它可以满足大多数游戏中角色自动导航的需求。 一、导航系统相关组件 Unity的导航系统由以下几个部分组成&#xff1a; Nav Mesh。Nav Mesh与具体的场景关联&…

Windows Qt 5.12.10下载与安装

Qt 入门实战教程&#xff08;目录&#xff09; C自学精简教程 目录(必读) 1 Qt下载 qt-opensource-windows-x86-5.12.10.exe 官方离线安装包&#xff1a;Download Source Package Offline Installers | Qt 下载巨慢 只能下载到最新的&#xff1a;https://download.qt.io/a…

JVM | 垃圾回收器(GC)- Java内存管理的守护者

引言 在编程世界中&#xff0c;有效的内存管理是至关重要的。这不仅确保了应用程序的稳定运行&#xff0c;还可以大大提高性能和响应速度。作为世界上最受欢迎的编程语言之一&#xff0c;通过Java虚拟机内部的垃圾回收器组件来自动管理内存&#xff0c;是成为之一的其中一项必…

Speech | 语音处理,分割一段音频(python)

本文主要是关于语音数据在处理过程中的一些脚本文件以及实例&#xff0c;所有代码只需要更改所需处理的文件路径&#xff0c;输出路径等&#xff0c;全部可运行。 目录 所需环境 方法1&#xff1a;将一整段音频按时间批量切成一个一个音频 方法2&#xff1a;将一整段音频按…

C++的类型转换

前言 我们都知道C是兼容C语言的在C语言中存在两种方式的类型转换&#xff0c;分别是隐式类型转换和显示类型转换&#xff08;强制类型转换&#xff09;&#xff0c;但是C觉得C语言的这套东西是够好&#xff0c;所以在兼容C语言的基础上又搞了一套自己的关于类型转换的东西。 目…

2023高教社杯数学建模C题思路代码 - 蔬菜类商品的自动定价与补货决策

# 1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…

mysql创建用户

创建用户 创建 -- 创建用户 itcast , localhost只能够在当前主机localhost访问, 密码123456; create user test01localhost identified by 123456;使用命令show databases;命令&#xff0c;只显示一个数据库&#xff0c;因为没有权限 -- 创建用户 test02, 可以在任意主机访问…

Stream API

Stream API执行流程 Stream API(java.util.stream)把真正的函数式编程风格引入到Java中,可以极大地提高程序员生产力&#xff0c;让程序员写出高效、简洁的代码 实际开发中项目中多数数据源都是来自MySQL、Oracle等关系型数据库,还有部分来自MongDB、Redis等非关系型数据库 …

vue3路由跳转params传参接收不到

import { useRouter } from "vue-router";const router useRouter(); // 提现记录 const withdrawalClick (item) > {router.push({ name: "Devwithdrawal", params: { name: 123 } }); };//跳转页面接收参数 import { useRoute } from "vue-rou…

网络是如何进行通信

网络是如何进行通信的 简介 在现代社会中&#xff0c;网络已经成为我们生活中不可或缺的一部分。从上网搜索信息、在线购物到远程工作和社交媒体&#xff0c;我们几乎无时无刻不与网络保持着联系。但是&#xff0c;网络究竟是个什么玩意&#xff0c;它是如何工作的呢&#xf…

Web服务器简介及HTTP协议

一、Web Server&#xff08;网页服务器&#xff09; 一个 Web Server 就是一个服务器软件&#xff08;程序&#xff09;&#xff0c;或者是运行这个服务器软件的硬件&#xff08;计算机&#xff09;。其主要功能是通过 HTTP 协议与客户端&#xff08;通常是浏览器&#xff08…

python可视化模块—快速利用matplot绘制图表

文章目录 一、Matplotlib基本介绍二、两种绘图方式区别&#xff08;plt.*** 和ax.***&#xff09;三、如何使用Matplotlib绘图1、画布—绘画的画板2、配置—更个性化的绘图全局配置局部配置面向对象绘图过程&#xff1a;ax代表子图变量过程式绘图过程 四、常用绘图图形如何选择…

配电房能源监测系统

配电房能源监测系统是一种能够实时监测和管理配电房能源消耗的系统&#xff0c;有助于企业更好地管理能源使用&#xff0c;降低能源成本&#xff0c;提高能源利用效率。本文将详细介绍配电房能源监测系统的组成、功能和优点。 一、配电房能源监测系统的组成 配电房能源监测系统…

网络通信深入解析:探索TCP/IP模型

http协议访问web 你知道在我们的网页浏览器的地址当中输入url&#xff0c;未必是如何呈现的吗&#xff1f; web浏览器根据地址栏中指定的url&#xff0c;从web服务器获取文件资源&#xff08;resource&#xff09;等信息&#xff0c;从而显示出web页面。web使用HTTP&#xff08…