我收到的代码不适用于多线程应用程序,现在我必须修改代码以支持多线程。

我有一个Singleton类(MyCenterSigltonClass),它基于以下内容的指令:
http://en.wikipedia.org/wiki/Singleton_pattern
我做到了线程安全

现在,我看到包含10-12个成员的类内部,其中一些具有getter/setter方法。
一些成员被声明为静态的,并且是类指针,例如:

static Class_A*    f_static_member_a;
static Class_B*    f_static_member_b;

对于这些成员,我在类(mutex_a)内定义了一个互斥锁(例如Class_A),我没有直接在MyCenterSigltonClass中添加互斥锁,原因是它们与MyCenterSigltonClass是一对一的关联,我认为我可以选择在MyCenterSigltonClass的类(Class_A)或(f_static_member_a)中定义互斥量。

1)我说的对吗?

另外,我的Singleton类(MyCenterSigltonClass)包含其他一些成员,例如
Class_C  f_classC;

对于这些类型的成员变量,我应该在MyCenterSigltonClass中为它们中的每个变量定义一个互斥体,以使其成为线程安全的吗?什么是处理这些案件的好方法?

感谢任何建议。

-尼玛

最佳答案

成员是否静态并不重要。如何保护成员变量实际上取决于如何从公共(public)方法访问它们。

您应该将互斥锁视为一种锁,可以保护某些资源免受并发的读/写访问。您不需要考虑保护内部类对象的必要性,而只是考虑其中的资源。您还需要考虑将要使用的锁的范围,尤其是在代码最初并非设计为多线程的情况下。让我举几个简单的例子。

A级
{
私有(private)的:
int mValuesCount;
int * mValues;

上市:
A(int count,int *值)
{
mValuesCount =计数;
mValues =(计数> 0)? new int [count]:NULL;
如果(mValues)
{
memcpy(mValues,values,count * sizeof(int));
}
}

int getValues(int count,int * values)常量
{
如果(mValues &&值)
{
memcpy(values,mValues,(count }
返回mValuesCount;
}
};

B级
{
私有(private)的:
A * mA;

上市:
B()
{
int values [5] = {1,2,3,4,5};
mA =新的A(5,值);
}
const A * getA()const {return mA; }
};

在此代码中,无需保护mA,因为没有机会在多个线程之间发生冲突。没有线程可以修改mA的状态,因此所有并发访问都仅从mA中读取。但是,如果我们修改类A:

A级
{
私有(private)的:
int mValuesCount;
int * mValues;

上市:
A(int count,int *值)
{
mValuesCount = 0;
mValues = NULL;
setValues(count,values);
}

int getValues(int count,int * values)常量
{
如果(mValues &&值)
{
memcpy(values,mValues,(count }
返回mValuesCount;
}

void setValues(int count,int * values)
{
删除[]个mValues;

mValuesCount =计数;
mValues =(计数> 0)? new int [count]:NULL;
如果(mValues)
{
memcpy(mValues,values,count * sizeof(int));
}
}
};

现在,我们可以有多个调用B::getA()的线程,并且一个线程可以从mA中读取,而另一个线程向mA中写入。考虑以下线程交互:

线程A:a-> getValues(maxCount,values);
线程B:a-> setValues(newCount,newValues);

当线程A在复制它时,线程B可能会删除mValues。在这种情况下,您将需要在类A中使用一个互斥对象来保护对mValues和mValuesCount的访问:

int getValues(int count,int * values)常量
{
//TODO:锁定互斥锁。
如果(mValues &&值)
{
memcpy(values,mValues,(count }
int returnCount = mValuesCount;
//TODO:解锁互斥锁。
return returnCount;
}

void setValues(int count,int * values)
{
//TODO:锁定互斥锁。
删除[] mValues;

mValuesCount =计数;
mValues =(计数> 0)? new int [count]:NULL;
如果(mValues)
{
memcpy(mValues,values,count * sizeof(int));
}
//TODO:解锁互斥锁。
}

这将防止对mValues和mValuesCount进行并发读/写。根据您环境中可用的锁定机制,您可以在getValues()中使用只读锁定机制,以防止多个线程在并发读取访问时阻塞。

但是,如果类A更复杂,您还需要了解实现锁的范围:

A级
{
私有(private)的:
int mValuesCount;
int * mValues;

上市:
A(int count,int *值)
{
mValuesCount = 0;
mValues = NULL;
setValues(count,values);
}

int getValueCount()const {return mValuesCount; }

int getValues(int count,int * values)常量
{
如果(mValues &&值)
{
memcpy(values,mValues,(count }
返回mValuesCount;
}

void setValues(int count,int * values)
{
删除[] mValues;

mValuesCount =计数;
mValues =(计数> 0)? new int [count]:NULL;
如果(mValues)
{
memcpy(mValues,values,count * sizeof(int));
}
}
};

在这种情况下,您可以进行以下线程交互:

线程A:int maxCount = a-> getValueCount();
线程A://为“maxCount” int值分配内存
线程B:a-> setValues(newCount,newValues);
线程A:a-> getValues(maxCount,values);

编写线程A就像对getValueCount()和getValues()的调用将是不间断的操作,但是线程B可能在线程A的操作中间更改了计数。根据新计数是大于还是小于原始计数,可能需要一段时间才能发现此问题。在这种情况下,将需要重新设计类A,或者需要提供某种事务支持,以便使用类A的线程可以阻塞/解除阻塞其他线程:

线程A:a-> lockValues();
线程A:int maxCount = a-> getValueCount();
线程A://为“maxCount” int值分配内存
线程B:a-> setValues(newCount,newValues);//阻塞,直到线程A调用unlockValues()
线程A:a-> getValues(maxCount,values);
线程A:a-> unlockValues();
线程B://完成对setValues()的调用

由于代码最初并不是为多线程设计的,因此您很可能会遇到这类问题,其中一个方法调用使用了较早调用中的信息,但从不担心对象状态在这两个方法之间的变化电话。

现在,开始想象一下,如果单例内的对象之间存在复杂的状态依赖关系,并且多个线程可以修改这些内部对象的状态,那将会发生什么。如果使用大量线程,这一切都会变得非常非常困惑,并且调试会变得非常困难。

因此,当您尝试使单例线程安全时,您需要查看对象交互的多层。一些好的问题要问:

  • 单例上的任何方法是否揭示可能在方法调用之间改变的内部状态(如我提到的上一个示例中所示)?
  • 是否向单例的客户显示了任何内部对象?
  • 如果是这样,这些内部对象上的任何方法是否会揭示可能在方法调用之间改变的内部状态?
  • 如果显示内部对象,它们是否共享任何资源或状态依赖项?

  • 如果您只是从内部对象读取状态,则可能不需要任何锁定(第一个示例)。您可能需要提供简单的锁定,以防止同时进行读/写访问(第二个示例)。您可能需要重新设计类或为客户端提供锁定对象状态的能力(第三个示例)。或者,您可能需要实现更复杂的锁定,其中内部对象在线程之间共享状态信息(例如,锁定Foo类中的资源需要锁定Bar类中的资源,但是锁定Bar类中的资源不一定需要锁定类Foo上的资源)。

    根据所有对象的交互方式,实现线程安全代码可能会成为一项复杂的任务。它可能比我在此给出的示例要复杂得多。只要确保您清楚地了解了类的使用方式以及它们之间的交互方式(并准备花一些时间来跟踪难以重现的错误)。

    10-05 18:59