关键部分 - 是还是否?

Posted

技术标签:

【中文标题】关键部分 - 是还是否?【英文标题】:Critical section - to be or not to be?关键部分 - 是还是不是? 【发布时间】:2008-12-26 18:00:37 【问题描述】:

我正在使用 WinSock2 和 WinAPI 函数编写聊天。而且我有点麻烦。 我将客户端连接的 std::vector 存储在服务器上。当新客户端连接时,新线程启动并且与客户端的所有工作都在这个新线程中完成。我不使用类(我知道它不是很好)所以这个连接列表只是定义为全局变量。 在我看来,可能是多个线程同时尝试访问该列表的情况。虽然我没有注意到这有什么问题,但我需要做这样的事情吗:


template 
class SharedVector 
    std::vector vect;
    CRITICAL_SECTION cs;
    SharedVector(const SharedVector& rhs) 
public:
    SharedVector();
    explicit SharedVector(const CRITICAL_SECTION& CS);
    void PushBack(const T& value);
    void PopBack();
    unsigned int size();
    T& operator[](int index);
    virtual ~SharedVector();
;

template
SharedVector::SharedVector() 
    InitializeCriticalSection(&cs);


template
SharedVector::SharedVector(const CRITICAL_SECTION& r): cs(r) 
    InitializeCriticalSection(&cs);


template
void SharedVector::PushBack(const T& value) 
    EnterCriticalSection(&cs);
    vect.push_back(value);
    LeaveCriticalSection(&cs);


template
void SharedVector::PopBack() 
    EnterCriticalSection(&cs);
    vect.pop_back();
    LeaveCriticalSection(&cs);

那么,我的情况需要使用 CRITICAL_SECTION 吗?我只是没有发现错误的幸运儿吗?

【问题讨论】:

【参考方案1】:

是的,您很幸运从未遇到任何问题。这是同步问题和竞争条件的问题,代码在 99.9% 的情况下都可以工作,而当灾难发生时,您将不知道为什么。

我会删除将 CRITICAL_SECTION 作为参数的构造函数,因为如果不查看(可能不存在的)文档以实现构造函数将对其进行初始化,则不清楚。

【讨论】:

【参考方案2】:

此代码不是异常安全的,向量 push_back 和 pop_back 方法可能会抛出异常,并且您可能会在此处出现死锁。 这种方法:

unsigned int size();
T& operator[](int index);

也必须同步,因为它们访问的数据可以被另一个线程修改。例如,您可以像这样访问数据:

value = shared_vector[shared_vector.size() - 1];

同时,另一个线程可以这样做:

shared_vector.PopBack();

【讨论】:

【参考方案3】:

我对您的最大问题是建筑。每个连接的线程真的需要直接访问这个 other 连接数组吗?他们真的不应该将某种抽象消息排入队列吗?如果每个连接线程都知道它所在的宇宙的实现细节,我预计你会在某个地方遇到麻烦。

但是让我们假设连接线程确实需要相互直接访问......

全局变量本身并不邪恶;学校只是教它,因为它比提供细致入微的理解更容易。您确实必须知道全局变量的确切含义,并且在您发现别无选择之前假设全局是错误的选择是一个不错的经验法则。解决许多问题的另一种方法是编写这样的函数:

SharedVector & GetSharedVector (void)

    static SharedVector sharedVector;
    return sharedVector;

与使用全局变量相比,这可以让您更好地控制何时实例化类,如果您在具有构造函数的全局变量之间存在依赖关系,这可能至关重要,尤其是当这些全局变量产生线程时。当有人想要访问这个“全局”变量时,它还让你有机会进行调解,而不是在他们直接戳它时无能为力地受苦。

其他一些想法也浮现在脑海中。排名不分先后……

您无疑已经知道 您在这里使用的模板语法是 奇怪的;我假设你刚刚输入 此代码无需编译即可。 你需要一个无所事事的任务 运营商在您的私人部分 防止事故(同样 原因你有一个无所事事的副本 构造函数)。 显式复制构造函数是 以多种方式破坏。它需要 显式复制向量或 您将在新的 SharedVector 实例中得到一个空向量。 它应该简单地初始化它的 自己的临界区而不是 复制它(然后初始化它)。 真的很适合你的情况 没有这个意义 完全起作用,因为网络 连接不合理 复制语义。 您可以使异常安全更容易 通过创建一个类 EnteredCriticalSection 其 构造函数调用 EnterCriticalSection 和谁 析构函数调用 离开关键部分。 (它需要 坚持参考 当然,关键部分。)这个 让安全变得更容易 序列化你的其他成员 面对异常时的功能。

【讨论】:

【参考方案4】:

是的,如果你像这样公开一个全局向量,你肯定需要在任何读/写时锁定它。是的,这会严重影响您的表现。

此外,每个请求一个新线程通常也不是一个好主意。为什么不重新设计应用程序以使用 IO 完成端口?

【讨论】:

【参考方案5】:

顺便说一句,将这个向量声明为全局变量并不是一个好方法。本地化会更好吗?

【讨论】:

以上是关于关键部分 - 是还是否?的主要内容,如果未能解决你的问题,请参考以下文章

确定 Windows 线程是不是在关键部分或类似部分?

在 AWS EFS 上使用 flock 来模拟关键部分是不是安全?

C#从mysql数据库获取锁定代码的关键部分

我可以使用联锁操作来更新多个值以避免锁定关键部分/互斥锁吗?

在模型上OBDSTAR X300 Pro3的关键MASTAR的功能反馈

iTunes 连接和关键字