zoukankan      html  css  js  c++  java
  • 学习:线程互斥

    线程安全问题: 当多个线程对同一个全局变量进行操作,并且进行写的操作的时候所引发的问题

    存在线程安全问题的代码:

    #include<windows.h>
    #include<stdio.h>
    
    int Tickets = 10;
    DWORD WINAPI  MyThreadFun_1(LPVOID pParameter) {
    	while (Tickets > 0){
    		printf("当前票还剩余%d张 Thread-1
    ", Tickets);
    		Tickets--;
    		printf("卖出一张 还剩%d张
    ", Tickets);
    	}
    	return 0;
    }
    
    DWORD WINAPI  MyThreadFun_2(LPVOID pParameter) {
    	while (Tickets > 0) {
    		printf("当前票还剩余%d张 Thread-2 
    ", Tickets);
    		Tickets--;
    		printf("卖出一张 还剩%d张
    ", Tickets);
    	}
    	return 0;
    }
    
    int main() {
    	HANDLE hThreadArr[2];
    	hThreadArr[0] = CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_1,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    	hThreadArr[1] = CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_2,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    	// 当线程执行完毕之后,恢复阻塞  ,该函数具有局限性 只能等待单个线程执行完毕的情况
    	WaitForMultipleObjects(2, hThreadArr,true, INFINITE);
    	// 线程被清理的两个必要条件:1、线程内核对象的计数器为0    2、线程的执行代码执行完毕 ,这里的话只有线程中执行完才会进行CloseHandle
    	CloseHandle(hThreadArr[0]); 
    	CloseHandle(hThreadArr[1]); 
    	getchar();
    	return 0;
    }
    


    临界资源: 临界资源是一次仅有一个线程使用的资源

    临界区: 访问临界资源的那段程序称为临界区

    解决方法:

    第一种实现方式:线程锁

    CRITICAL_SECTION 线程锁结构体
    InitializeCriticalSection 初始化线程锁结构体
    EnterCriticalSection 固定锁
    LeaveCriticalSection 释放锁
    

    实现代码如下:

    #include<windows.h>
    #include<stdio.h>
    
    int Tickets = 10;
    
    CRITICAL_SECTION myLock;
    
    
    DWORD WINAPI  MyThreadFun_1(LPVOID pParameter) {
    	while (true){
    		EnterCriticalSection(&myLock);
    		if (Tickets == 0) {
    			break;
    		}
    		printf("当前票还剩余%d张 Thread-1
    ", Tickets);
    		Tickets--;
    		printf("卖出一张 还剩%d张
    ", Tickets);
    		LeaveCriticalSection(&myLock);
    	}
    	return 0;
    }
    
    
    int main() {
    	InitializeCriticalSection(&myLock);
    
    	HANDLE hThreadArr[2];
    	hThreadArr[0] = CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_1,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    
    	hThreadArr[1] = CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_1,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    
    
    	// 当线程执行完毕之后,恢复阻塞  ,该函数具有局限性 只能等待单个线程执行完毕的情况
    	WaitForMultipleObjects(2, hThreadArr,true, INFINITE);
    
    
    	// 线程被清理的两个必要条件:1、线程内核对象的计数器为0    2、线程的执行代码执行完毕 ,这里的话只有线程中执行完才会进行CloseHandle
    	CloseHandle(hThreadArr[0]); 
    	CloseHandle(hThreadArr[1]); 
    	getchar();
    	return 0;
    }
    

    第二种实现方式:互斥体

    互斥体利用的是Windows互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问,在线程同步与保证程序单体运行上都有相当大的用处。

    特点:互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。

    单进程实现的线程互斥

    #include<Windows.h>
    #include<stdio.h>
    
    DWORD WINAPI  MyThreadFun(LPVOID pParameter) {
    	for (int i = 100; i > 0; i--) {
    		printf("%d---MyThreadFun
    ", i);
    		Sleep(1000);
    	}
    	return 0;
    }
    
    int main() {
    	HANDLE mutex;
    
    	mutex = CreateMutex(NULL, false, "MyMutex"); //创建互斥体,名称为MyMutex
    
    	WaitForSingleObject(mutex, INFINITE); //等待获得互斥体
    
    	for (int i = 0; i < 10; i++) {  //进行
    		printf("A进程----%d 
    ", i);
    		Sleep(1000);
    	}
    
    	ReleaseMutex(mutex);//释放互斥体
    	getchar();
    	return 0;
    
    }
    

    多进程线程互斥:

    #include<Windows.h>
    #include<stdio.h>
    
    HANDLE mutex;
    int money = 100;
    
    DWORD WINAPI  MyThreadFun_1(LPVOID pParameter) {
    	while (money > 0){
    		WaitForSingleObject(mutex, INFINITE); //等待获取互斥对象
    		if (money == 0) {
    			break;
    		}
    		money--;
    		printf("%d	Thread-1
    ", money);
    		Sleep(50);
    		ReleaseMutex(mutex);//释放互斥体
    	}
    	return 0;
    }
    
    DWORD WINAPI  MyThreadFun_2(LPVOID pParameter) {
    	while (money > 0) {
    		WaitForSingleObject(mutex, INFINITE); //等待获取互斥对象
    		if (money == 0) {
    			break;
    		}
    		money--;
    		printf("%d	Thread-2
    ", money);
    		Sleep(50);
    		ReleaseMutex(mutex);//释放互斥体
    	}
    	return 0;
    }
    
    int main() {
    	HANDLE aThread[2];
    	mutex = CreateMutex(NULL, false, "MyMutex"); //创建互斥体,名称为MyMutex
    	aThread[0]= CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_1,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    	aThread[1] = CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_2,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    	getchar();
    
    	CloseHandle(aThread[0]);
    	CloseHandle(aThread[1]);
    
    	return 0;
    
    }
    

    线程锁与互斥体的区别:

    1、线程锁只能用于单个进程内部的线程控制,互斥体能用于单个进程,也可以多个进程
    2、互斥体可以设定等待超时,但线程锁不能
    3、线程意外终结时,Mutex可以避免无限等待,体现的地方比如意外exit了,而用互斥体实现线程同步,另一个进程中互斥体的句柄进行的线程还是会继续下去,不会一直进行等待
    4、Mutex效率没有线程锁高


    第三种实现方法:事件

    自己还是有点不太理解

    #include<Windows.h>
    #include<stdio.h>
    
    int money = 100;
    HANDLE eve;
    /*
    HANDLE WINAPI CreateEvent(
      __in          LPSECURITY_ATTRIBUTES lpEventAttributes, //事件对象的安全属性,如果为Null不能被子进程继承If this parameter is NULL, the handle cannot be inherited by child processes
      __in          BOOL bManualReset,//事件对象的类型,TRUE表示人工重置事件对象,FLASE表示自动重置事件对象;
      __in          BOOL bInitialState,//事件初始状态 true通知状态;flase 未通知状态
      __in          LPCTSTR lpName//事件对象的名称If lpName is NULL, the event object is created without a name
    );
    */
    
    DWORD WINAPI  MyThreadFun_1(LPVOID pParameter) {
    	while (money > 0){
    		WaitForSingleObject(eve, INFINITE); // 等待,直到事件对象收到信号
    		money--;
    		Sleep(50);
    		printf("%d	Thread-1
    ", money);
    		SetEvent(eve); // 释放事件对象,并且把当前线程挂起,再发信号唤醒给另外的线程
    	}
    	return 0;
    }
    
    DWORD WINAPI  MyThreadFun_2(LPVOID pParameter) {
    	while (money > 0) {
    		WaitForSingleObject(eve, INFINITE);// 等待,直到事件对象收到信号
    		money--;
    		Sleep(50);
    		printf("%d	Thread-2
    ", money);
    		SetEvent(eve);  //释放事件对象,并且把当前线程挂起,再发信号唤醒给另外的线程
    	}
    	return 0;
    }
    
    int main() {
    
    	HANDLE arrThread[2];
    	eve = CreateEvent(NULL, false,true, NULL);
    	// CreateEvent 第二个参数可以理解为 是否为通知类型,当设置为true的时候则为通知类型 线程都执行 ,false则为互斥 只有一个线程可以执行
    
    	//第三个参数 为 初始有无信号,若无信号 则需要我们手动SetEvent
    
    
    	arrThread[0]= CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_1,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    
    	arrThread[1] = CreateThread(
    		NULL,  //获取默认的安全描述符,当前用户的令牌权限 
    		0,  //使用可执行文件的默认大小
    		MyThreadFun_2,  // 创建线程调用的函数
    		NULL,  // 传递函数中的参数
    		0, //线程在创建后立即运行 
    		NULL // 不返回线程标识符
    	);
    
    	//SetEvent(eve); //设置为有信号,如果这里写了的话,那么CreateEvent的第三个参数需要设置为false
    
    	WaitForMultipleObjects(2, arrThread, true, INFINITE); //等待多线程执行完成
    	CloseHandle(arrThread[0]);
    	CloseHandle(arrThread[1]);
    	CloseHandle(eve); //关闭内核对象
    	getchar();
    	return 0;
    }
    
  • 相关阅读:
    【Intellij Idea】设置JDK
    MarkDown换行
    Git 查看/修改用户名、邮箱
    JavaScript对象
    Javascript事件
    第十次会议
    第九次会议
    详细设计文档
    第八次会议
    第七次会议
  • 原文地址:https://www.cnblogs.com/zpchcbd/p/12249758.html
Copyright © 2011-2022 走看看