返回值的锁

本文探讨了多线程编程中的锁机制,将其比喻为一块告示牌,并分享了一个项目中遇到的因缺乏适当同步机制而导致的问题及解决方案。通过封装锁和使用宏,作者展示了如何在现有代码基础上增加必要的同步措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    在多线程编程中,时刻需要注意加锁,这也是多线程编程中的一个难点。

    但是我们所用到的锁都是“协议锁”,即是一个“君子协定”,所谓“防君子不防小人”。这也是多线程编程中比较让人肾疼的地方。

    我觉得用“锁”这个名词对编程中的这种操作现象进行描述其实是不太合适的,反而会引起歧义,把它看成是门口挂的一块“告示牌”会更合适一些 ——— 一面写着“有人勿进”,一面写着“空闲可用”。

    你的线程作为一个“君子”,在到达访问临界资源的门(门上是没有锁的,也没办法给它装个锁)前,首先要看看这块告示牌,如果写着的是“有人勿进”,那么你就在门口等着,时不时再瞄一眼,看告示牌是不是翻了个面了,也可能是在你等得快睡着的时候被人叫醒。

    否则你就可以进去,顺便把优雅的将告示牌翻个面。

    等你从门里出来的时候,或许你可以看见门口也等了一大堆像你一样的君子了,那么就看你是默默翻转告示牌离开,还是和他们打声招呼了。

    如果所有的人都像你这么君子作风,那么应该没什么问题了,井然有序,虽然可能办事效率会很低啦。然而总保不准会有一两个非君子的小人,进门不看告示牌,不管三七二十一,冲进去拿了东西就走,还不顺手把门口的告示牌给翻一下。

    当然如果他进门的时候刚好是“空闲可用”状态,或者即便是有人,但他拿的东西也不是别人想要的,那么顶多也就是虚惊一场。怕就怕的是起了冲突,那么很不幸,你的软件可能就要崩溃了。。


下面说说我遇到的比较肾疼的问题:

    接手的项目实在是惨不忍睹。有太多诸如定义了一个单例类对象,必然是需要当多个线程访问的临界资源的,然而却提供着 static MyCls& MyCls::GetMyCls();这种借口。然而本来应该提供的需要在内部加锁的“增删改查”接口可能却压根没有!!!

    如果只是一个类这样写还好,自己重构一下问题不大,然而当整个项目几乎都这样的时候,我也无能为力。

    面对偶发并且频率极高的崩溃,也只能死马当活马医,加锁吧。。。

    需要在定义这个类的模块中增加锁成员,并且导出出去供其他模块使用,多么肾疼的写法。。。

    好吧,只好用C语言强大万能的宏来包装一下,搞一个返回值的锁吧。

    代码如下:

    1. 封装锁:

//CriticalSection_Lock.h
#pragma once

#include <windows.h>

class CriticalSection_Lock
{
public:
    CriticalSection_Lock(void);
    ~CriticalSection_Lock(void);
    //{重载new delete解决跨模块的析构
    void *operator new(size_t size){return ::operator new(size);}
    void *operator new[](size_t size){return ::operator new(size);}
    void operator delete(void *p){return ::operator delete(p);}
    void operator delete[](void *p){return ::operator delete(p);}
    //}}
private:
    static CRITICAL_SECTION *m_cs;
};

#define CS_LOCK(name)   { CriticalSection_Lock lock_##name;

#define CS_UNLOCK(name) }

//CriticalSection_Lock.cpp
#include "CriticalSection_Lock.h"

CRITICAL_SECTION *CriticalSection_Lock::m_cs = nullptr;

CriticalSection_Lock::CriticalSection_Lock(void)
{
    if (nullptr == m_cs)
    {
        static CRITICAL_SECTION cs;//实际的代码不应该这样写,也不是这么写的,这里只是给个简单的例子,用了一个全局的锁。不过即便像这样的示例代码,也是有问题的,需要使用双检锁避免创建多个cs实例(如果真的需要使用全局单例的话)
        m_cs = &cs;
        InitializeCriticalSectionEx(m_cs,4000,0);
    }
    EnterCriticalSection(m_cs);
}


CriticalSection_Lock::~CriticalSection_Lock(void)
{
    LeaveCriticalSection(m_cs);
}
    2.返回值锁相关的宏:
//RetValueLock.h
#pragma once

#define RETURN_NEED_LOCK

#ifdef RETURN_NEED_LOCK
#define RV_WITH_LOCK_DECL(Type)                                                                 \
struct RetWithLock##Type                                                                        \
{                                                                                               \
    RetWithLock##Type(Type type, std::shared_ptr<CriticalSection_Lock> sp)                      \
        :m_type(type)                                                                           \
        ,m_sp(sp){}                                                                             \
    Type m_type;                                                                                \
    std::shared_ptr<CriticalSection_Lock> m_sp;                                                 \
};

#define RV_WITH_LOCK(Type) RetWithLock##Type

#define RETURN_RET_WITH_LOCK(Type,name)                                                         \
    CriticalSection_Lock *pcs = new CriticalSection_Lock;                                       \
    std::shared_ptr<CriticalSection_Lock> spint(pcs);                                           \
    return RetWithLock##Type(name,spint);

#define GET_REAL_RET_FROM_LOCKRET(Name) Name.m_type

#else

#define RV_WITH_LOCK_DECL(Type)
#define RV_WITH_LOCK(Type) Type
#define RETURN_RET_WITH_LOCK(Type,name) return name;
#define GET_REAL_RET_FROM_LOCKRET(Name) Name

#endif
    3.使用:
//1.typedef成一个可以放进标识符的名字
typedef MyCls& MyClsRef;
//2.替换GetMyCls();等申明和实现
//MyCls& MyCls::GetMyCls()
RV_WITH_LOCK(MyClsRef) MyCls::GetMyCls()
{
    //....
}
//3.替换外部调用GetMyCls()的地方
///MyCls& v = MyCls::GetMyCls();
RV_WITH_LOCK(MyClsRef) lv = GetVct();
MyClsRef v = GET_REAL_RET_FROM_LOCKRET(lv);

    然后基本上几个简单的正则替换就搞定了,崩溃是没有了,然而至于性能,呵呵,管他呢,毕竟我只是个擦屁股的,我还能奢望我自己做得有多好呢[笑哭脸]。。。

### POSIX信号量 `sem_wait` 函数返回值及其含义 #### 返回值说明 `sem_wait` 是用于处理POSIX信号量的核心函数之一,其主要功能是对指定的信号量执行P操作。此函数的行为取决于目标信号量的当前值: - **成功情况**: 如果信号量的值大于0,则将其减去1,并立即返回。此时,函数返回值为0,表示操作成功[^2]。 - **失败情况**: 当信号量的值等于0时,调用线程会被阻塞(即进入睡眠状态),直到信号量的值变为正数为止。一旦条件满足,线程将继续执行并将信号量值减1后返回0[^2]。 #### 错误码解释 在某些情况下,`sem_wait` 可能不会正常完成操作,而是返回特定的错误码以指示具体原因: - **EAGAIN**: 此错误仅适用于 `sem_trywait` 而不适用于标准的 `sem_wait` 。然而,在文档中提到的是,当尝试通过 `sem_trywait` 对一个已经处于零值的信号量进行操作时,将直接返回 EAGAIN ,而不是使线程陷入等待状态[^3]。 - **其他可能的错误**: - **EINVAL**: 提供给函数的参数无效或者信号量对象未正确初始化。 - **EFAULT**: 参数指向的位置非法或不可访问。 以下是实现的一个简单例子展示如何使用 `sem_wait` 和检查它的返回值: ```c #include <stdio.h> #include <semaphore.h> int main() { sem_t sem; if (sem_init(&sem, 0, 0) != 0) { // 初始化信号量为0 perror("Failed to initialize semaphore"); return 1; } printf("Attempting to wait on semaphore...\n"); if (sem_wait(&sem) == 0) { // 成功获取 printf("Semaphore acquired successfully.\n"); } else { perror("Error while waiting on semaphore"); // 处理错误 } sem_destroy(&sem); return 0; } ``` 上述代码展示了基本的信号量初始化以及等待过程中的异常捕获机制。 #### 原子性和并发控制的重要性 值得注意的一点是,无论是减少还是增加信号量的操作都应当保持原子性,这意味着这些动作不应该被任何外部中断打断,从而确保多线程环境下的数据一致性[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值