《TCP/IP网络编程》第20章 Windows中的线程同步
同步方法分类及CRITICAL_SECTION同步
用户模式(User mode)和内核模式(Kernal mode)
Windows操作系统运行方式(程序运行方式)是双模式操作(Dual-mode Operation),用户模式和内核模式之间切换。
- 用户模式:运行应用程序的基本模式,禁止访问物理设备,而且会限制访问的内存区域。
- 内核模式:操作系统运行时的模式,硬件设备和内存区域访问无限制。
2种模式为了提高安全性,应用程序的运行时错误会破坏操作系统及各种资源。
与内核对象有关的所有事务(资源创建等)都在内核模式下进行。频繁模式切换会影响性能。
用户模式同步
无需操作系统的帮助而在应用程序级别进行的同步,速度快,使用相对简单,功能上存在一定局限性。
内核模式同步
- 能够提供更多功能
- 可以指定超时,防止产生死锁。
可以跨越进程进行线程同步,可以进行不同进程之间的同步(因为基于内核对象的操作),用户模式和内核模式切换,性能受到一定影响。
基于CRITICAL_SECTION的同步
属于用户同步,需要CRITICAL_SECTION(非内核对象)。
#include <windows.h>
void InitializeCriticalsection(LPCRITICAL_SECTION lpCriticalSection);
//销毁CRITICAL_SECTION对象使用过的(CRITICAL_SECTION对象相关的)资源。
void DeleteCriticalsection(LPCRITICAL_SECTION lpCriticalSection);
#include <windows.h>
void EnterCriticalsection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalsection(LPCRITICAL_SECTION lpCriticalSection);
20.SyncCS_win.c
#include <stdio.h>
#include <windows.h>
#include <process.h>
#define NUM_THREAD 50
long long num = 0;
CRITICAL_SECTION cs;
unsigned WINAPI thread_inc(void *arg)
{
EnterCriticalSection(&cs);
for (int i = 0; i < 50000000; i++)
num += 1;
LeaveCriticalSection(&cs);
return 0;
}
unsigned WINAPI thread_des(void *arg)
{
EnterCriticalSection(&cs);
for (int i = 0; i < 50000000; i++)
num -= 1;
LeaveCriticalSection(&cs);
return 0;
}
int main(int argc, char *argv[])
{
InitializeCriticalSection(&cs);
HANDLE thread_id[NUM_THREAD];
for (int i = 0; i < NUM_THREAD; i++)
if (i % 2)
thread_id[i] = (HANDLE)_beginthreadex(NULL, 0, thread_inc, NULL, 0, NULL);
else
thread_id[i] = (HANDLE)_beginthreadex(NULL, 0, thread_des, NULL, 0, NULL);
WaitForMultipleObjects(NUM_THREAD, thread_id, TRUE, INFINITE);
DeleteCriticalSection(&cs);
printf("result: %lld\n", num);
return 0;
};
// gcc 20.SyncCS_win.c -o 20.SyncCS_win && 20.SyncCS_win
内核模式的同步方法
基于互斥量(Mutual Exclusion)对象的同步
#include <windows.h>
//成功互斥量对象句柄,失败NULL
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全相关配置信息,NULL默认
BOOL bInitialOwner, //TRUE,互斥量对象属于该函数线程,同时进入non-signaled状态;FALSE,互斥量对象属于不属于任何线程,此时为signaled状态
LPCTSTR lpName //命名互斥量对象,NULL无名对象
);
//销毁内核对象(互斥量,信号量,事件等)
BOOL CloseHandle(HANDLE hObject);
//获取互斥量
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
//释放互斥量
BOOL ReleaseMutex(HANDLE hMutex);
互斥量被某一线程获取(拥有)时为non-signaled状态,释放(未拥有)时进入signaled状态。
WaitForSingleObject函数验证互斥量是否已分配,调用结果:
- 阻塞状态:互斥量已被其他线程获取,现处于non-signaled状态。
- 直接返回:其他线程未占用互斥量对象,现处于signaled状态。
互斥量对象是auto-reset模式的内核对象,在WaitForSingleObject函数返回时自动进入non-signaled状态。
20.SyncMutex_win.c
#include <stdio.h>
#include <windows.h>
#include <process.h>
#define NUM_THREAD 50
long long num = 0;
HANDLE hMutex;
unsigned WINAPI thread_inc(void *arg)
{
WaitForSingleObject(hMutex, INFINITE);
for (int i = 0; i < 50000000; i++)
num += 1;
ReleaseMutex(hMutex);
return 0;
}
unsigned WINAPI thread_des(void *arg)
{
WaitForSingleObject(hMutex, INFINITE);
for (int i = 0; i < 50000000; i++)
num -= 1;
ReleaseMutex(hMutex);
return 0;
}
int main(int argc, char *argv[])
{
hMutex = CreateMutex(NULL, FALSE, NULL);
HANDLE thread_id[NUM_THREAD];
for (int i = 0; i < NUM_THREAD; i++)
if (i % 2)
thread_id[i] = (HANDLE)_beginthreadex(NULL, 0, thread_inc, NULL, 0, NULL);
else
thread_id[i] = (HANDLE)_beginthreadex(NULL, 0, thread_des, NULL, 0, NULL);
WaitForMultipleObjects(NUM_THREAD, thread_id, TRUE, INFINITE);
CloseHandle(hMutex);
printf("result: %lld\n", num);
return 0;
};
// gcc 20.SyncMutex_win.c -o 20.SyncMutex_win && 20.SyncMutex_win
基于信号量对象的同步
#include <windows.h>
//成功互斥量对象句柄,失败NULL
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全相关配置信息,NULL默认
LONG lInitialCount, //信号量的初始值,应大于等于0小于等于lMaximumCount
LONG lMaximumCount, //信号量的最大值,为1时,信号量是只能表示0或1的二进制信号量
LPCTSTR lpName //命名信号量对象,NULL无名对象
);
//释放信号量
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount, //释放增加信号量的值,该参数指定增加的值,超过最大值则不增加,返回FALSE
LPLONG lpPreviousCount //保存修改前值的变量地址,不需要传递NULL
);
信号量对象的值大于0时成为signaled状态,为0时成为non-signaled状态。
调用WaitForSingleObject函数,信号量大于0才会返回,同时信号量值减1(信号量减1后等于0时,进入non-signaled状态)。
20.SyncSema_win.c
#include <stdio.h>
#include <windows.h>
#include <process.h>
static HANDLE sem_one;
static HANDLE sem_two;
static int num;
unsigned WINAPI read(void *arg)
{
for (int i = 0; i < 5; i++)
{
fputs("Input num: ", stdout); // 0
WaitForSingleObject(sem_two, INFINITE); // 1
scanf("%d", &num);
ReleaseSemaphore(sem_one, 1, NULL); // 2
}
return 0;
}
unsigned WINAPI accu(void *arg)
{
int sum = 0;
for (int i = 0; i < 5; i++)
{
WaitForSingleObject(sem_one, INFINITE); // 3
sum += num;
ReleaseSemaphore(sem_two, 1, NULL); // 4
}
printf("Result: %d\n", sum);
return 0;
}
int main(int argc, char *argv[])
{
sem_one = CreateSemaphore(NULL, 0, 1, NULL);
sem_two = CreateSemaphore(NULL, 1, 1, NULL);
HANDLE id_t1 = (HANDLE)_beginthreadex(NULL, 0, read, NULL, 0, 0);
HANDLE id_t2 = (HANDLE)_beginthreadex(NULL, 0, accu, NULL, 0, 0);
WaitForSingleObject(id_t1, INFINITE);
WaitForSingleObject(id_t2, INFINITE);
CloseHandle(sem_one);
CloseHandle(sem_two);
return 0;
};
// gcc 20.SyncSema_win.c -o 20.SyncSema_win && 20.SyncSema_win
基于事件对象的同步
事件对象可以自主选择non-signaled状态运行的auto-reset模式或manual-reset模式。
事件对象的主要特点是可以创建manual-reset模式的对象。
#include <windows.h>
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,//安全相关配置信息,NULL默认
BOOL bMaunalReset, //TRUE, manual-reset;FALSE, auto-reset
BOOL bInitialState, //TRUE, signaled状态;FALSE,non-signaled状态
LPCTSTR lpName //命名信号量对象,NULL无名对象
);
//manual-rest模式时,即使WaitForSingleObject函数返回也不会回到non-signaled状态。
BOOL ResetEvent(HANDLE hEvent); //to the non-signaled
BOOL SetEvent(HANDLE hEvent); //to the signaled
20.SyncEvent_win.c
#include <stdio.h>
#include <windows.h>
#include <process.h>
#define STR_LEN 100
static char str[STR_LEN];
static HANDLE hEvent;
unsigned WINAPI NumberOfA(void *arg)
{
int cnt = 0;
WaitForSingleObject(hEvent, INFINITE); // 2,线程阻塞
for (int i = 0; str[i] != 0; i++)
if (str[i] == 'A')
cnt++;
printf("Num of A: %d\n", cnt);
return 0;
}
unsigned WINAPI NumberOfOthers(void *arg)
{
int cnt = 0;
WaitForSingleObject(hEvent, INFINITE); // 2,线程阻塞
for (int i = 0; str[i] != 0; i++)
if (str[i] != 'A')
cnt++;
printf("Num of others: %d\n", cnt);
return 0;
}
int main(int argc, char *argv[])
{
// non-signaled状态创建manual-reset模式的事件对象
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE id_t1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, 0);
HANDLE id_t2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, 0);
fputs("Input string: ", stdout);
fgets(str, STR_LEN, stdin); // 0
// 事件设置为signaled状态
// id_t1和id_t2两个线程同时摆脱等待状态(manual-reset模式,事件对象会一直处于signaled状态)
// 触发两个线程
SetEvent(hEvent); // 1,激活线程
WaitForSingleObject(id_t1, INFINITE); // 3,线程等待
WaitForSingleObject(id_t2, INFINITE); // 4,线程等待
// 事件设置为not-signaled状态
ResetEvent(hEvent);
CloseHandle(hEvent);
return 0;
};
// gcc 20.SyncEvent_win.c -o 20.SyncEvent_win && 20.SyncEvent_win
Windows平台下多线程服务器端
20.chat_server_win.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>
#define PORT 9999
#define BUF_SIZE 100
#define MAX_CLNT 256
HANDLE mutx;
int clnt_cnt = 0;
SOCKET clnt_socks[MAX_CLNT];
void ErrorHanding(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
void send_msg(SOCKET clnt_sock, char *msg, int len)
{
WaitForSingleObject(mutx, INFINITE);
for (int i = 0; i < clnt_cnt; i++)
if (clnt_socks[i] != clnt_sock)
send(clnt_socks[i], msg, len, 0);
ReleaseMutex(mutx);
}
unsigned WINAPI handle_clnt(void *arg)
{
SOCKET clnt_sock = *((SOCKET *)arg);
char msg[BUF_SIZE];
int str_len;
while ((str_len = recv(clnt_sock, msg, sizeof(msg), 0)) != 0)
send_msg(clnt_sock, msg, str_len);
WaitForSingleObject(mutx, INFINITE);
closesocket(clnt_sock);
for (int i = 0; i < clnt_cnt; i++)
if (clnt_sock == clnt_socks[i])
{
for (; i < clnt_cnt - 1; i++)
clnt_socks[i] = clnt_socks[i + 1];
break;
}
clnt_cnt--;
ReleaseMutex(mutx);
return 0;
}
int main(int argc, char *argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHanding("WSAStartup() error!");
mutx = CreateMutex(NULL, FALSE, NULL);
SOCKET serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == INVALID_SOCKET)
ErrorHanding("socket() error!");
int opt = 1;
if (setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0)
ErrorHanding("setsockopt() error!");
int addr_size = sizeof(SOCKADDR_IN);
SOCKADDR_IN serv_addr;
memset(&serv_addr, 0, addr_size);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
if (bind(serv_sock, (SOCKADDR *)&serv_addr, addr_size) == SOCKET_ERROR)
ErrorHanding("bind() error!");
if (listen(serv_sock, 5) == SOCKET_ERROR)
ErrorHanding("listen() error!");
while (1)
{
SOCKADDR_IN clnt_addr;
SOCKET clnt_sock = accept(serv_sock, (SOCKADDR *)&clnt_addr, &addr_size);
WaitForSingleObject(mutx, INFINITE);
clnt_socks[clnt_cnt++] = clnt_sock;
ReleaseMutex(mutx);
HANDLE t_id = (HANDLE)_beginthreadex(NULL, 0, handle_clnt, (void *)&clnt_sock, 0, NULL);
printf("Connected client IP: %s:%d\n", inet_ntoa(clnt_addr.sin_addr), clnt_addr.sin_port);
}
closesocket(serv_sock);
CloseHandle(mutx);
WSACleanup();
return 0;
}
// gcc 20.chat_server_win.c -o 20.chat_server_win -lws2_32 && 20.chat_server_win
20.chat_client_win.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>
#include <time.h>
#include <winsock2.h>
#define IP "127.0.0.1"
#define PORT 9999
#define BUF_SIZE 1024
#define NAME_SIZE 20
char *gen_random_str(char *str, int length)
{
srand((unsigned)time(NULL));
for (int i = 0; i < length; i++)
{
switch (rand() % 2)
{
case 0:
str[i] = 'A' + rand() % 26;
break;
case 1:
str[i] = 'a' + rand() % 26;
break;
}
}
str[length] = '\0';
return str;
}
void ErrorHanding(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
unsigned WINAPI send_msg(void *arg)
{
char name[NAME_SIZE] = "DEFAULT";
gen_random_str(name, 7);
SOCKET sock = *((SOCKET *)arg);
while (1)
{
char msg[BUF_SIZE] = {0};
fgets(msg, BUF_SIZE - 1, stdin);
if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n"))
{
// closesocket(sock);
shutdown(sock, SD_SEND);
exit(0);
}
char name_msg[NAME_SIZE + BUF_SIZE + 3];
sprintf(name_msg, "[%s] %s", name, msg);
send(sock, name_msg, strlen(name_msg), 0);
}
return 0;
}
unsigned WINAPI recv_msg(void *arg)
{
SOCKET sock = *((SOCKET *)arg);
while (1)
{
char name_msg[NAME_SIZE + BUF_SIZE] = {0};
int str_len = recv(sock, name_msg, NAME_SIZE + BUF_SIZE - 1, 0);
if (str_len == -1)
return -1;
else if (str_len == 0)
break;
else
{
name_msg[str_len] = 0;
fputs(name_msg, stdout);
}
}
closesocket(sock);
return 0;
}
int main(int argc, char *argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHanding("WSAStartup() error!");
SOCKET sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
ErrorHanding("socket() error!");
int addr_size = sizeof(SOCKADDR_IN);
SOCKADDR_IN addr;
memset(&addr, 0, addr_size);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(IP);
addr.sin_port = htons(PORT);
if (connect(sock, (SOCKADDR *)&addr, addr_size) == SOCKET_ERROR)
ErrorHanding("connect() error!");
HANDLE snd_thread = (HANDLE)_beginthreadex(NULL, 0, send_msg, (void *)&sock, 0, NULL);
HANDLE rcv_thread = (HANDLE)_beginthreadex(NULL, 0, recv_msg, (void *)&sock, 0, NULL);
WaitForSingleObject(snd_thread, INFINITE);
WaitForSingleObject(rcv_thread, INFINITE);
closesocket(sock);
WSACleanup();
return 0;
}
// gcc 20.chat_client_win.c -o 20.chat_client_win -lws2_32 && 20.chat_client_win