我有一个庞大的全球结构阵列。阵列的某些区域与各个线程相关联,并且这些线程可以修改其阵列的区域,而无需使用关键部分。但是阵列中有一个特殊区域,所有线程都可以访问它。访问数组这些部分的代码需要仔细使用关键部分(每个数组元素都有自己的关键部分),以防止两个线程同时写入结构的任何可能性。
现在我有一个我试图追逐的神秘错误,它正在发生不可预测且很少发生。似乎其中一个结构充满了一些不正确的数字。一个明显的解释是,当应该排除这个数字时,意外地允许另一个线程设置这个数字。
不幸的是,似乎几乎无法跟踪此错误。每次出现坏数据的数组元素都不同。我希望能够做的是为bug设置一些陷阱,如下所示:我将为数组元素N输入一个关键部分,然后我知道没有其他线程应该能够触及数据,然后(直到我退出关键部分)为调试工具设置某种标志,说“如果有任何其他线程试图在这里更改数据,请打破并向我显示令人讨厌的源代码补丁”......但我怀疑没有这样的工具存在。 ..或者是吗?或者我应该采用一些完全不同的调试方法。
答案 0 :(得分:2)
如何使用透明的互斥类包装数据?然后,您可以应用其他锁定状态检查。
class critical_section;
template < class T >
class element_wrapper
{
public:
element_wrapper(const T& v) : val(v) {}
element_wrapper() {}
const element_wrapper& operator = (const T& v) {
#ifdef _DEBUG_CONCURRENCY
if(!cs->is_locked())
_CrtDebugBreak();
#endif
val = v;
return *this;
}
operator T() { return val; }
critical_section* cs;
private:
T val;
};
至于关键部分的实施:
class critical_section
{
public:
critical_section() : locked(FALSE) {
::InitializeCriticalSection(&cs);
}
~critical_section() {
_ASSERT(!locked);
::DeleteCriticalSection(&cs);
}
void lock() {
::EnterCriticalSection(&cs);
locked = TRUE;
}
void unlock() {
locked = FALSE;
::LeaveCriticalSection(&cs);
}
BOOL is_locked() {
return locked;
}
private:
CRITICAL_SECTION cs;
BOOL locked;
};
实际上,可以使用critical_section::locked
(如果成功后跟::TryEnterCriticalSection
)而不是自定义::LeaveCriticalSection
标志来确定是否拥有关键部分。虽然,上面的实现几乎一样好。
所以适当的用法是:
typedef std::vector< element_wrapper<int> > cont_t;
void change(cont_t::reference x) { x.lock(); x = 1; x.unlock(); }
int main()
{
cont_t container(10, 0);
std::for_each(container.begin(), container.end(), &change);
}
答案 1 :(得分:0)
我知道处理此类错误的两种方法:
1)反复阅读代码,寻找可能的错误。我可以考虑两个可能导致这种情况的错误:不同步的访问或不正确的内存地址写入。也许你有更多的想法。
2)记录,记录日志记录。在每个关键位置添加许多可选跟踪(OutputDebugString或日志文件),其中包含足够的信息 - 索引,变量值等。最好使用#ifdef添加此跟踪。重现错误并尝试从日志中了解会发生什么。
答案 2 :(得分:0)
您最好(最快)的赌注仍然是修改互斥代码。正如你所说,这是一个明显的解释 - 为什么不试图真正找到解释(通过逻辑)而不是额外的提示(通过编码)可能会产生不确定的?如果代码审查没有结果有用,您仍然可以使用互斥代码并将其用于测试运行。第一次尝试不应该重现系统中的错误,而是确保正确实现互斥锁 - 实现线程(从2开始向上),所有线程都试图一次又一次地访问相同的数据结构,并且每个都有一个随机的小延迟他们让他们在时间线上抖动。如果这个测试导致了一个你在代码中无法识别的有缺陷的互斥锁,那么你已经成为一些架构依赖效应的受害者(可能是工程重新排序,多核缓存不兼容等),并且需要找到另一个互斥实现。如果OTOH你在互斥锁中发现了一个明显的错误,试着在你的真实系统中利用它(检测你的代码,以便更频繁地出现错误),这样你就可以确保它确实是原始问题的原因。 / p>
答案 3 :(得分:0)
我在蹬踏工作时正在思考这个问题。处理此问题的一种可能方法是,当未通过关键部分所有权主动访问和保护存储器的部分时,使其成为只读存储器。这假设问题是由于线程在没有相应的临界区时写入内存引起的。
这有一些限制,阻止它工作。最重要的是,我认为你只能逐页设置权限(我相信4K)。因此,您可能需要对分配方案进行一些非常具体的更改,以便缩小相应的部分以进行保护。第二个问题是,如果另一个线程主动拥有临界区,它将不会捕获写入内存的流氓线程。但如果关键部分不属于它,它会捕获它并导致立即访问冲突。
我们的想法是将您的EnterCriticalSection调用更改为:
EnterCriticalSection()
VirtualProtect( … PAGE_READWRITE … );
将LeaveCriticalSection调用更改为:
VirtualProtect( … PAGE_READONLY … );
LeaveCriticalSection()
以下代码块显示对VirtualProtect
int main( int argc, char* argv[] 1
{
unsigned char *mem;
int i;
DWORD dwOld;
// this assume 4K page size
mem = malloc( 4096 * 10 );
for ( i = 0; i < 10; i++ )
mem[i * 4096] = i;
// set the second page to be readonly. The allocation from malloc is
// not necessarily on a page boundary, but this will definitely be in
// the second page.
printf( "VirtualProtect res = %d\n",
VirtualProtect( mem + 4096,
1, // ends up setting entire page
PAGE_READONLY, &dwOld ));
// can still read it
for ( i = 1; i < 10; i++ )
printf( "%d ", mem[i*4096] );
printf( "\n" );
// Can write to all but the second page
for ( i = 0; i < 10; i++ )
if ( i != 1 ) // avoid second page which we made readonly
mem[i] = 1;
// this causes an access violation
mem[4096] = 1;
}