《TCP/IP网络编程》第20章 Windows中的线程同步

92 篇文章 18 订阅
34 篇文章 3 订阅

同步方法分类及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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值