我收到的代码不适用于多线程应用程序,现在我必须修改代码以支持多线程。
我有一个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上的资源)。
根据所有对象的交互方式,实现线程安全代码可能会成为一项复杂的任务。它可能比我在此给出的示例要复杂得多。只要确保您清楚地了解了类的使用方式以及它们之间的交互方式(并准备花一些时间来跟踪难以重现的错误)。