2

我正在使用 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 吗?我只是没有发现错误的幸运儿吗?

4

5 回答 5

7

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

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

于 2008-12-26T18:10:34.687 回答
3

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

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

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

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

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

shared_vector.PopBack();
于 2008-12-26T19:23:10.810 回答
3

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

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

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

SharedVector & GetSharedVector (void)
{
    static SharedVector sharedVector;
    return sharedVector;
}

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

许多其他想法也浮现在脑海中。没有特别的顺序...

  • 您无疑已经知道您在这里使用的模板语法很奇怪;我假设你只是输入了这段代码而没有编译它。
  • 您需要在私有部分中使用无操作赋值运算符来防止意外(出于同样的原因,您在那里有一个无操作复制构造函数)。
  • 显式复制构造函数以多种方式被破坏。它需要显式复制向量,否则您将在新的 SharedVector 实例中得到一个空向量。它应该简单地初始化它自己的临界区而不是复制它(然后初始化它)。真的,对于您的情况,拥有此功能可能根本没有意义,因为网络连接没有合理的复制语义。
  • 通过创建一个类 EnteredCriticalSection,其构造函数调用 EnterCriticalSection 并且其析构函数调用 LeaveCriticalSection,可以使异常安全更容易。(当然,它需要保留对临界区的引用。)这使得在面对异常时安全地序列化其他成员函数变得更加容易。
于 2009-01-08T03:02:46.310 回答
1

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

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

于 2008-12-26T18:09:22.333 回答
0

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

于 2008-12-26T18:15:49.403 回答