windows 线程同步的四种方式总结

一:内核态下的三种同步方式:

        一、互斥变量Mutex)

        互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

        创建互斥对象:调用函数CreateMutex。调用成功,该函数返回所创建的互斥对象的句柄。

        释放指定互斥对象的所有权:调用ReleaseMutex函数。线程访问共享资源结束后,线程要主动释放对互斥对象的所有权,使该对象处于已通知状态。

        创建互斥对象函数 

HANDLE
WINAPI
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,   //指向安全属性
    _In_ BOOL bInitialOwner,   //初始化互斥对象的所有者  TRUE 立即拥有互斥体
    _In_opt_ LPCWSTR lpName    //指向互斥对象名的指针  L“Bingo”
);

  • 第一个参数表示安全属性,这是每一个创建内核对象都会有的参数,NULL表示默认安全属性
  • 第二个参数表示互斥对象所有者,TRUE立即拥有互斥体
  • 第三个参数表示指向互斥对象的指针 
  • 例子1:
  • 下面这段程序声明了一个全局整型变量,并初始化为0。一个线程函数对这个变量进行+1操作,执行50000次;另一个线程函数对这个变量-1操作,执行10000次。两个线程函数各创建15个。因为我们使用了互斥变量,30个线程会按照一定顺序对这变量操作,因此最后结果为0。 

  • #include <stdio.h>
    #include <windows.h>
    #include <process.h>

    #define NUM_THREAD    30
    unsigned WINAPI threadInc(void* arg);
    unsigned WINAPI threadDes(void* arg);
    long long num = 0;
    HANDLE hMutex;

    int main() {
        //内核对象数组
        HANDLE tHandles[NUM_THREAD];
        int i;
        //创建互斥信号量
        hMutex = CreateMutex(0, FALSE, NULL);
        for (i = 0; i < NUM_THREAD; i++) {
            if (i % 2)
                tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
            else
                tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
        }

        WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
        //关闭互斥对象
        CloseHandle(hMutex);
        printf("result: %lld \n", num);
        return 0;
    }

    unsigned WINAPI threadInc(void* arg) {
        int i;
        //请求使用
        WaitForSingleObject(hMutex, INFINITE);
        for (i = 0; i < 100000; i++)
            num += 1;
        //释放
        ReleaseMutex(hMutex);
        return 0;
    }
    unsigned WINAPI threadDes(void* arg) {
        int i;
        //请求
        WaitForSingleObject(hMutex, INFINITE);
        for (i = 0; i < 100000; i++)
            num -= 1;
        //释放
        ReleaseMutex(hMutex);
        return 0;
    }

2) 实例2:

#define _AFXDLL
#include "afxmt.h"
#include "iostream"
using namespace std;
int array1[10];
CMutex Section;

UINT WrtThrd(LPVOID param)
{
Section.Lock();
for (int x = 0; x < 10; x++)
{
  array1[x] = x + 6;
  printf("first %d\n", array1[x]);

}
    Sleep(80);
Section.Unlock();
return 0;
}
int main(int argc, char* argv[])
{
    DWORD ThrdID;
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WrtThrd, NULL, 0, &ThrdID);
    Sleep(15);
    Section.Lock(); //获取互斥对象
    for (int x = 0; x < 10; x++)
        printf("second %d\n", array1[x]);
    Section.Unlock(); //释放互斥对象
    return 0;
}

输出: 

        二:事件对象 (Event)    

         事件对象也属于内核对象,它包含以下三个成员:

  • 使用计数;
  • 用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值;
  • 用于指明该事件处于已通知状态还是未通知状态的布尔值。

事件对象有两种类型:人工重置的事件对象和自动重置的事件对象。这两种事件对象的区别在于当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程;而当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。

1)创建事件对象 

 调用CreateEvent函数创建或打开一个命名的或匿名的事件对象。

HANDLE CreateEvent(   
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性   
BOOL bManualReset,   // 复位方式  TRUE 必须用ResetEvent手动复原  FALSE 自动还原为无信号状态
BOOL bInitialState,   // 初始状态   TRUE 初始状态为有信号状态  FALSE 无信号状态
LPCTSTR lpName     //对象名称  NULL  无名的事件对象 
);

2. 设置事件对象状态

调用SetEvent函数把指定的事件对象设置为有信号状态。

3. 重置事件对象状态

调用ResetEvent函数把指定的事件对象设置为无信号状态。

4. 请求事件对象

 线程通过调用WaitForSingleObject函数请求事件对象。

代码示例 

下面这段程序是一段火车售票:线程A和B会不停的购票直到票数小于0,执行完毕。在判断票数前会先申请事件对象,购票结束或者票数小于0时则会释放事件对象(事件对象置位有信号)。因为我们使用了事件对象。两个线程会按某一顺序购票,直到票数小于0。     

#include<iostream>
#include<Windows.h>
#include<process.h>
using namespace std;

//火车站卖票
int iTickets = 100;//总票数
HANDLE g_hEvent;


unsigned WINAPI SellTicketA(void* lpParam) {

    while (true) {
        WaitForSingleObject(g_hEvent, INFINITE);
        if (iTickets > 0) {
            Sleep(1);
            printf("A买了一张票,剩余%d\n", iTickets--);
        }
        else {
            SetEvent(g_hEvent);
            break;
        }
        SetEvent(g_hEvent);
    }
    return 0;
}

unsigned WINAPI SellTicketB(void* lpParam) {
    while (true) {
        WaitForSingleObject(g_hEvent, INFINITE);
        if (iTickets > 0) {
            Sleep(1);
            printf("B买了一张票,剩余%d\n", iTickets--);
        }
        else {
            SetEvent(g_hEvent);
            break;
        }
        SetEvent(g_hEvent);
    }
    return 0;
}

int main() {

    HANDLE hThreadA, hThreadB;
    hThreadA = (HANDLE)_beginthreadex(NULL, 0, SellTicketA, NULL, 0, NULL);
    hThreadB = (HANDLE)_beginthreadex(NULL, 0, SellTicketB, NULL, 0, NULL);

    CloseHandle(hThreadA); //只是关闭了一个线程句柄对象,表示我不再使用该句柄,即不对这个句柄对应的线程做任何干预了。并没有结束线程。
    CloseHandle(hThreadB);


    printf("Before CreateEvent\n");
    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    SetEvent(g_hEvent);
    Sleep(4000);
    CloseHandle(g_hEvent);
    system("pause");
    return 0;
}

 输出结果如下:

三、资源信号量

         信号量(semaphore)是操作系统用来解决并发中的互斥和同步问题的一种方法。与互斥量不同的地方是,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。

        创建信号量函数:

        HANDLE    WINAPI    
CreateSemaphoreW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,  // Null 安全属性
    _In_ LONG lInitialCount,  //初始化时,共有多少个资源是可以用的。 0:未触发状//态(无信号状态),表示没有可用资源
    _In_ LONG lMaximumCount,  //能够处理的最大的资源数量   
    _In_opt_ LPCWSTR lpName   //NULL 信号量的名称
);

  • 第一个参数表示安全属性,这是创建内核对象函数都会有的参数,NULL表示默认安全属性
  • 第二个参数表示初始时有多少个资源可用,0表示无任何资源(未触发状态)
  • 第三个参数表示最大资源数
  • 第四个参数表示信号量的名称,NULL表示无名称的信号量对象

        //  等待信号量

        信号灯有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于 0 时为有信号状态,小于等于 0 时为无信号状态,所以可以利用 WaitForSingleObject 进行等待,当 WaitForSingleObject 等待成功后信号灯的值会被减少 1,直到释放时信号灯会被增加 1 。

        DWORD WaitForMultipleObjects(
          DWORD nCount,                     // 等待的对象数量
          CONST HANDLE *lpHandles,  // 对象句柄数组指针
          BOOL fWaitAll,                        // 等待方式,
          //为TRUE表示等待全部对象都变为有信号状态才返回,为FALSE表示任何一个对象变为有信号状态则返回
          DWORD dwMilliseconds         // 超时设置,以ms为单位,如果为INFINITE表示无限期的等待
); 

        增加/释放信号量

        ReleaseSemaphore(
    _In_ HANDLE hSemaphore,   //信号量的句柄
    _In_ LONG lReleaseCount,   //将lReleaseCount值加到信号量的当前资源计数上面 0-> 1
    _Out_opt_ LPLONG lpPreviousCount  //当前资源计数的原始值
);

  • 第一个参数表示信号量句柄,也就是调用创建信号量函数时返回的句柄
  • 第二个参数表示释放的信号量个数,该值必须大于0,但不能大于信号量的最大计数
  • 第三个参数表示指向要接收信号量的上一个计数的变量的指针。如果不需要上一个计数, 则此参数可以为NULL 。

        关闭句柄 :

        CloseHandle(
            _In_ _Post_ptr_invalid_ HANDLE hObject );

        代码示例:

        下面这段程序创建了两个信号资源,其最大资源都为1;一个初始资源为0,另一个初始资源为1。线程中的for循环每执行一次会将另一个要申请的信号资源的可用资源数+1。因此程序的执行结果为两个线程中的for循环交替执行。 

#include<iostream>
#include<Windows.h>
#include<process.h>
using namespace std;

static HANDLE semOne;
static HANDLE semTwo;
static int num;

/*
* 信号资源semOne初始为0,最大1个资源可用
* 信号资源semTwo初始为1,最大1个资源可用
*/

unsigned WINAPI Read(void* arg) {
    int i;
    for (i = 0; i < 5; i++) {
        fputs("Input num:\n", stdout);
        printf("begin read\n");
        WaitForSingleObject(semTwo, INFINITE);
        printf("beginning read\n");
        scanf_s("%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");
        WaitForSingleObject(semOne, INFINITE);
        printf("beginning Accu\n");
        sum += num;
        printf("sum=%d\n", sum);
        ReleaseSemaphore(semTwo, 1, NULL);
    }
    return 0;
}

int main() {
    HANDLE hThread1, hThread2;
    semOne = CreateSemaphore(NULL, 0, 1, NULL);//初始值没有可用资源
    semTwo = CreateSemaphore(NULL, 1, 1, NULL);//初始值有一个可用资源


    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;
}

 

二:用户态下的同步方式:

        一 、关键代码

        关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。 

        1、初始化关键代码段

        调用Initialize CriticalSection函数初始化一个关键代码段:

       Initialzie CriticalSection(  _Out_ LPRRITICAL_SECTION lpCriticalSection );

         该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前,首先需要构造一个CRITICAL_SCTION结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection函数。

        2、进入关键代码

        VOID WINAPI EnterCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection);
        3、退出关键代码段

        VOID WINAPI LeaveCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection);
         4、删除临界区

WINBASEAPI VOID WINAPI DeleteCriticalSection(  _Inout_ LPCRITICAL_SECTION lpCriticalSection);

        当临界区不再需要时,可以调用DeleteCriticalSection函数释放该对象,该函数将释放一个没有被任何线程所拥有的临界区对象的所有资源。

程序实例:

        下面这段程序同样也是火车售票,其工作逻辑与上面的事件对象基本吻合。

#include<iostream>
#include<Windows.h>
#include<process.h> 
using namespace std;

int iTickets = 100;
CRITICAL_SECTION g_cs;

//A窗口
DWORD WINAPI SellTicketA(void* lpParam) {
    while (1) {
        EnterCriticalSection(&g_cs);//进入临界区
        if (iTickets > 0) {
            iTickets--;
            printf("A买了一张票,剩余票数为:%d\n", iTickets);
        }
        else {
            LeaveCriticalSection(&g_cs);
            break;
        }
        LeaveCriticalSection(&g_cs);
        Sleep(10);
    }
    return 0;
}

//B窗口
DWORD WINAPI SellTicketB(void* lpParam) {
    while (1) {
        EnterCriticalSection(&g_cs);
        if (iTickets > 0) {
            iTickets--;
            printf("B买了一张票,剩余票数为:%d\n", iTickets);
        }
        else {
            LeaveCriticalSection(&g_cs);
            break;
        }
        LeaveCriticalSection(&g_cs);
        Sleep(10);
    }
    return 0;
}

int main() {
    HANDLE hThreadA, hThreadB;
    hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);
    hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);

    CloseHandle(hThreadA);
    CloseHandle(hThreadB);

    InitializeCriticalSection(&g_cs);//初始化关键代码
    Sleep(6000);

    DeleteCriticalSection(&g_cs);
    system("pause");
    return 0;
}

 

         

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

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

相关文章

Vue的学习之旅-part5

Vue的学习之旅-part5 虚拟DOM的原理用JS模拟DOM结构 vue的方法、计算属性、过滤器computed:{} 计算属性computed计算属性的完全体computed计算属性和methods方法的区别&#xff1a;过滤器&#xff1a;filters:{ 多个方法 } Vuex 状态管理模式 前几篇博客: Vue的学习之旅-part1 …

【算法】第二篇 大衍数列

导航 1. 简介2. 数列特征3. 代码演示 1. 简介 大衍数列&#xff0c;来源于《乾坤谱》中对易传“大衍之数五十”的推论。主要用于解释中国传统文化中的太极衍生原理。数列中的每一项&#xff0c;都代表太极衍生过程中&#xff0c;曾经经历过的两仪数量总和。是中华传统文化中隐…

A Study of Network Forensic Investgation in Docker Environments文章翻译

A Study of Network Forensic Investgation in Docker Environments Docker环境下的网络取证研究 摘要 网络罪犯利用越来越多的技术(如虚拟机或基于容器的基础设施)进行恶意活动。 这些虚拟环境的固有动态简化了恶意服务的快速创建,并隐藏了所涉及的系统,这是以前没有的技…

用AI作图,使用这个免费网站,快看我画的大鹏鸟和美女

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

中科院发布大模型想象增强法IAG,无需外部资源,想想就能变强

在人工智能领域&#xff0c;尤其是自然语言处理&#xff08;NLP&#xff09;的子领域——问答系统&#xff08;QA&#xff09;中&#xff0c;知识的获取和利用一直是推动技术进步的核心问题。近年来&#xff0c;大语言模型&#xff08;LLMs&#xff09;在各种任务中展现出了惊人…

风电场智能化转型基于ARM工控机的HDMI数据实时监控显示

全球能源结构不断调整的大背景下&#xff0c;智能电网、太阳能发电、风能发电等清洁能源领域正经历着一场由技术创新引领的深刻变革。在这场变革中&#xff0c;ARM架构的工控机凭借其出色的性能、低功耗及高度可定制化的特点&#xff0c;正在成为能源管理系统的核心组件&#x…

轴向磁通电机应用场景不断扩展 未来市场存在较大开发空间

轴向磁通电机应用场景不断扩展 未来市场存在较大开发空间 根据磁通方向不同&#xff0c;磁通电机分为轴向磁通电机、径向磁通电机两大类&#xff0c;其中轴向磁通电机的磁通方向为轴向&#xff0c;载流导体系径向放置。轴向磁通电机特点在于结构上旋转转子位于定子的侧面&#…

【算法统治世界】动态规划 个人笔记总结

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

面试字节被挂了

分享一个面试字节的经历。 1、面试过程 一面&#xff1a;上来就直接"做个题吧"&#xff0c;做完之后&#xff0c;对着简历上一个项目聊&#xff0c;一直聊到最后&#xff0c;还算比较正常。 二面&#xff1a;做自我介绍&#xff0c;花几分钟聊了一个项目&#xff…

数据库入门-----SQL基础知识

目录 &#x1f4d6;前言&#xff1a; &#x1f4d1;SQL概述&&通用语法&#xff1a; &#x1f433;DDL&#xff1a; &#x1f43b;操作数据库&#xff1a; &#x1f41e;数据类型&#xff1a; &#x1f989;操作表&#xff1a; &#x1f9a6;DML: 语法规则&#x…

浅析安全传输协议HTTPS之“S”

当前互联网&#xff0c;在各大浏览器厂商和CA厂商的推动下&#xff0c;掀起了一股HTTPS应用浪潮。为了让大家更好的了解HTTPS&#xff0c;本文给大家介绍关于HTTPS 中的S一个整体的认识。从其产生的历史背景、设计目标说起&#xff0c;到分析其协议设计结构、交互流程是如何实现…

R语言数据操纵:常用函数

目录 处理循环的函数 lapply函数 apply函数 mapply函数 tapply函数 split函数 排序的函数 sort函数与order函数 总结数据信息的函数 head函数与tail函数 summary函数 str函数 table函数 any函数 all函数 xtab函数 object.size函数 这篇文章主要介绍R语言中处理…

HarmonyOS 开发-一镜到底“页面转场”动画

介绍 本方案做的是页面点击卡片跳转到详情预览的转场动画效果 效果图预览 使用说明 点击首页卡片跳转到详情页&#xff0c;再点击进入路由页面按钮&#xff0c;进入新的路由页面 实现思路 首页使用了一种视觉上看起来像是组件的转场动画&#xff0c;这种转场动画通常是通过…

swiftui macOS实现加载本地html文件

import SwiftUI import WebKitstruct ContentView: View {var body: some View {VStack {Text("测试")HTMLView(htmlFileName: "localfile") // 假设你的本地 HTML 文件名为 index.html.frame(minWidth: 100, minHeight: 100) // 设置 HTMLView 的最小尺寸…

RabbitMQ-延迟队列的使用

目录 一、使用场景 二、第一种方式&#xff1a;创建具有超时功能且绑定死信交换机的消息队列 三、第二种方式&#xff1a;创建通用延时消息 四、第三种方式&#xff1a;使用rabbitmq的延时队列插件&#xff0c;实现同一个队列中有多个不同超时时间的消息&#xff0c;并按时间…

春秋之境28512

题目说该CMS的/single.php路径下&#xff0c;id参数存在一个SQL注入漏洞。访问看一下随便点一个图片。 发现了注入点?id 那么开始查看闭合符一个 就报错了 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for th…

一起学习python——基础篇(10)

前言&#xff0c;Python 是一种面向对象的编程语言。以前大学读书的时候经常开玩笑说的一句话“如果没有对象&#xff0c;就new一个”。起因就是编程老师上课时经常说一句“首先&#xff0c;我们new一个对象”。 今天讲一下python的类和对象。 类是什么&#xff1f;它是一种用…

【linux】基础IO(三)

上一节基础IO我们着重理解了重定向与缓冲区&#xff0c;这节我们需要重点理解文件再磁盘中是怎样存储。以及上一节我们没有涉及到的知识。 stderr到时有什么用&#xff1f; 目录 fd-> 0 1 2&#xff1a;初步理解2怎样将错误与正确输出都打印在一个文件&#xff1f; 文件在硬…

Redis基础操作与持久化

目录 引言 一、Reids工具与数据类型 &#xff08;一&#xff09;Reids工具 &#xff08;二&#xff09;Redis数据类型 1.String&#xff08;字符串&#xff09; 2.Hash&#xff08;哈希&#xff09; 3.List&#xff08;列表&#xff09; 4.Set&#xff08;集合&#xff…

实践笔记-linux内核版本升级(centos7)

linux内核版本升级 1.查看当前内核版本信息2.采用yum方式进行版本升级2.1导入仓库源2.2选择 ML 或 LT 版本安装2.3设置内核启动 3.删除旧版本内核 1.查看当前内核版本信息 #查看操作系统版本 cat /etc/redhat-release #查看系统内核 uname -r2.采用yum方式进行版本升级 2.1导…