线程的同步

一、线程同步概念:

线程同步是指在多线程编程中,为了保证多个线程之间的数据访问和操作的有序性以及正确性,需要采取一些机制来协调它们的执行。在多线程环境下,由于线程之间是并发执行的,可能会出现竞争条件(Race Condition)等问题,从而导致程序的不稳定性和错误。

案例与猜想:

示例1:

#include <iostream>
#include <windows.h>

int g;

DWORD WINAPI MyThreadProc (LPVOID lp){
    for(int i = 0; i < 100000000; i++){
        g++;
    }
    return 0;
}

int main(){
    HANDLE h = CreateThread(NULL, 0, MyThreadProc, NULL, 0, 0);
    
    for(int i = 0; i < 100000000; i++){
        g++;
    }

    std::cout << g << std::endl;
    
    CloseHandle(h);
    return 0;
}

结果:

这里测试了两个次,当多个线程同时对公共资源进行操作时,会发生错误,该示例结果始终处在 

 100000000~200000000之间

猜测一:主线程可能先走完,然后输出g,并退出。

示例2:

因此在这里继续添加一个等待线程函数(WaitForSingleObject)等待线程结束试试

发现依旧无法达到预期。

 猜测二:线程与线程之间存在同步,使得g在某一个或某一些值时,g在主线程与线程中,只增加一次。

二、解决方法

常见的线程同步机制包括:

        1. 互斥锁(Mutex):互斥锁是一种保护共享资源的机制,它确保在任意时刻只有一个线程能够访问被保护的资源。当一个线程获得了互斥锁,其他线程就需要等待锁的释放才能访问资源。

        2. 信号量(Semaphore):信号量是一种用于控制同时访问某一资源的线程数目的方法。它可以允许多个线程同时访问资源,但是可以通过信号量的计数来控制同时访问的线程数量。

        3. 条件变量(Condition Variable):条件变量用于在某些特定条件下使线程等待或唤醒。它通常与互斥锁一起使用,以实现在满足特定条件时线程的阻塞和唤醒。

        4. 读写锁(Read-Write Lock):读写锁允许多个线程同时读取共享资源,但是在写操作时需要互斥锁来保护资源,以避免多个线程同时写入导致数据不一致性。

        5. 原子操作(Atomic Operations):原子操作是一种不可分割的操作,能够保证在多线程环境下的执行不会被中断,从而避免竞争条件。

线程同步的目的是确保线程之间的协调和有序执行,以避免数据竞争和其他并发问题。选择合适的线程同步机制取决于具体的应用场景和需求。

1.互斥锁(Mutex):

PS:临界区同样存在计数机制,进入几次临界区,就要退出几次临界区

示例:

#include <iostream>
#include <windows.h>

int g;

CRITICAL_SECTION g_cs;//创建互斥锁

DWORD WINAPI MyThreadProc (LPVOID lp){
    for(int i = 0; i < 100000000; i++){
        EnterCriticalSection(&g_cs);    //进入临界区
        g++;
        LeaveCriticalSection(&g_cs);    //离开临界区
    }
    return 0;
}

int main(){
    InitializeCriticalSection(&g_cs);//初始化互斥锁

    HANDLE h = CreateThread(NULL, 0, MyThreadProc, NULL, 0, 0);
    

    for(int i = 0; i < 100000000; i++){
        EnterCriticalSection(&g_cs);    //进入临界区
        g++;
        LeaveCriticalSection(&g_cs);    //进入临界区
    }

    WaitForSingleObject(h, INFINITE);

    std::cout << g << std::endl;
    
    CloseHandle(h);
    DeleteCriticalSection(&g_cs);
    return 0;
}

结果:

需要注意的是,过多地使用互斥锁可能会导致性能问题,因为只有一个线程能够执行临界区代码,其他线程需要等待

2.信号量(Semaphore)

信号量用于限制多个线程对共享资源的访问数量。在 Windows 中,你可以使用 CreateSemaphorestd::semaphore 来创建信号量。

CreateSemaphore函数:用来创建信号

HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 指向安全描述符的指针,控制信号量的安全性,通常设置为 NULL
  LONG                  lInitialCount,         // 初始计数值,即初始的信号量计数
  LONG                  lMaximumCount,         // 最大计数值,即信号量的最大值
  LPCTSTR               lpName                 // 信号量的名称,可以为 NULL
);

  1. 初始计数(Initial Count): 初始计数是信号量在创建时的初始状态。它指定了在创建信号量时可以立即使用的可用信号量数量。初始计数告诉你在开始时有多少个许可证(信号量),可供线程或进程使用。

  2. 最大计数(Maximum Count): 最大计数指定了信号量允许的最大并发访问数量。它限制了在信号量上等待的线程或进程的数量。当信号量的计数达到最大值时,任何试图等待该信号量的线程将会被阻塞,直到有其他线程释放信号量。

ReleaseSemaphore函数: 释放信号量

BOOL ReleaseSemaphore(
  HANDLE hSemaphore,  // 信号量的句柄
  LONG   lReleaseCount, // 释放的信号量计数值
  LPLONG lpPreviousCount // 指向变量的指针,用于存储之前的信号量计数值(可选)
);
 

信号量为0时,表示无信号,返回false;反之,整数表示有信号,返回true;

示例:

#include <iostream>
#include <windows.h>

int g;
HANDLE semaphore;

DWORD WINAPI myThreadProc(LPVOID lp) {
    WaitForSingleObject(semaphore, INFINITE);
    for (int i = 0; i < 10000000; i++) {
        g++;
    }
    ReleaseSemaphore(semaphore, 1, NULL);

    return 0;
}

int main() {
    semaphore = CreateSemaphore(NULL, 1, 1, NULL);

    HANDLE h = CreateThread(NULL, 0, myThreadProc, NULL, 0, NULL);

    WaitForSingleObject(semaphore, INFINITE);
    for (int i = 0; i < 10000000; i++) {
        g++;
    }
    ReleaseSemaphore(semaphore, 1, NULL);

    WaitForSingleObject(h, INFINITE);

    std::cout << g << std::endl;

    CloseHandle(h);
    CloseHandle(semaphore);

    return 0;
}

结果:

 友情提醒:如果把WaitForSignalObject()放在循环里面,会特别慢,因为每一次循环都要等待信号与释放信号

3.条件变量:

在 Windows 平台上,条件变量的常见实现是使用事件对象(Event Object)来实现。下面是一个使用事件对象作为条件变量的示例:

#include <iostream>
#include <windows.h>

int g;
HANDLE event;

DWORD WINAPI MyThreadProc(LPVOID lp)
{

    for (int i = 0; i < 10000000; i++)
    {
        g++;
    }
    SetEvent(event); // 设置事件,通知主线程
    return 0;
}

int main()
{
    event = CreateEvent(NULL, FALSE, FALSE, NULL); // 创建事件对象,初始状态为未触发状态

    HANDLE h = CreateThread(NULL, 0, MyThreadProc, NULL, 0, NULL);

    WaitForSingleObject(event, INFINITE); // 等待事件触发
    for (int i = 0; i < 10000000; i++)
    {
        g++;
    }
    SetEvent(event);
    
    CloseHandle(h);
    CloseHandle(event);

    std::cout << g << std::endl;

    return 0;
}

CreateEvent函数参数如下:

  1. lpEventAttributes:一个指向 SECURITY_ATTRIBUTES 结构的指针,用于指定事件对象的安全属性。可以设置为 NULL,表示使用默认的安全属性。

  2. bManualReset:一个布尔值,指定事件对象的重置方式。

    • 如果为 TRUE,则表示事件对象是手动重置的。这意味着当一个线程通过调用 SetEvent 函数将事件设置为触发状态后,事件将保持触发状态,直到某个线程显式地通过调用 ResetEvent 函数将其重置为非触发状态。
    • 如果为 FALSE,则表示事件对象是自动重置的。这意味着当一个线程通过调用 SetEvent 函数将事件设置为触发状态后,事件将自动在第一个等待线程成功等待后被重置为非触发状态。
  3. bInitialState:一个布尔值,指定事件对象的初始状态。

    • 如果为 TRUE,表示事件对象初始状态为触发状态(对于手动重置和自动重置事件都一样)。
    • 如果为 FALSE,表示事件对象初始状态为非触发状态。
  4. lpName:一个指向以 null 结尾的字符串的指针,用于指定事件对象的名称。可以设置为 NULL,表示事件没有名称。

4.读写锁 

示例如下:

#include <iostream>
#include <windows.h>

SRWLOCK srwLock; // SRW 锁对象

DWORD WINAPI ReaderThread(LPVOID lp)
{
    AcquireSRWLockShared(&srwLock); // 获取共享锁(读锁)

    // 读取共享资源...

    ReleaseSRWLockShared(&srwLock); // 释放共享锁

    return 0;
}

DWORD WINAPI WriterThread(LPVOID lp)
{
    AcquireSRWLockExclusive(&srwLock); // 获取独占锁(写锁)

    // 修改共享资源...

    ReleaseSRWLockExclusive(&srwLock); // 释放独占锁

    return 0;
}

int main()
{
    InitializeSRWLock(&srwLock); // 初始化 SRW 锁对象

    HANDLE readerThread = CreateThread(NULL, 0, ReaderThread, NULL, 0, NULL);
    HANDLE writerThread = CreateThread(NULL, 0, WriterThread, NULL, 0, NULL);

    WaitForSingleObject(readerThread, INFINITE);
    WaitForSingleObject(writerThread, INFINITE);

    CloseHandle(readerThread);
    CloseHandle(writerThread);

    return 0;
}

PS:再vscode下AcquireSRWLockShared()会报错,再vs下没问题

 SRW(Slim Reader/Writer)是一种轻量级的读写锁

在这个示例中,我们使用了 AcquireSRWLockShared 函数来获取共享锁(读锁),并使用 ReleaseSRWLockShared 函数来释放共享锁。同时,我们使用了 AcquireSRWLockExclusive 函数来获取独占锁(写锁),并使用 ReleaseSRWLockExclusive 函数来释放独占锁。

要注意,SRW 锁适用于一种常见的读多写少的情况,它具有较低的开销。如果你需要更复杂的读写锁机制,例如支持多个读者和写者的同步,可能需要使用更高级的同步机制,如互斥锁和条件变量的组合等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值