我能否将一项关键任务应用程序基于此测试的结果,即100个线程读取一个主线程设置的十亿次指针永远不会流泪?
除了撕扯还有其他潜在的问题吗?
这里是使用g++ -g tear.cxx -o tear -pthread
编译的独立演示。
#include <atomic>
#include <thread>
#include <vector>
using namespace std;
void* pvTearTest;
atomic<int> iTears( 0 );
void TearTest( void ) {
while (1) {
void* pv = (void*) pvTearTest;
intptr_t i = (intptr_t) pv;
if ( ( i >> 32 ) != ( i & 0xFFFFFFFF ) ) {
printf( "tear: pv = %p\n", pv );
iTears++;
}
if ( ( i >> 32 ) == 999999999 )
break;
}
}
int main( int argc, char** argv ) {
printf( "\n\nTEAR TEST: are normal pointer read/writes atomic?\n" );
vector<thread> athr;
// Create lots of threads and have them do the test simultaneously.
for ( int i = 0; i < 100; i++ )
athr.emplace_back( TearTest );
for ( int i = 0; i < 1000000000; i++ )
pvTearTest = (void*) (intptr_t)
( ( i % (1L<<32) ) * 0x100000001 );
for ( auto& thr: athr )
thr.join();
if ( iTears )
printf( "%d tears\n", iTears.load() );
else
printf( "\n\nTEAR TEST: SUCCESS, no tears\n" );
}
实际的应用程序是malloc()
的数组,有时是realloc()
的数组(大小为2的幂;重新分配存储量的两倍),许多子线程绝对会在关键任务中发挥作用,但在高性能方面,关键方式。
线程有时会需要向该数组添加一个新条目,这将通过将下一个数组条目设置为指向某个对象,然后增加atomic<int> iCount
来实现。最后,它将数据添加到某些数据结构中,这将导致其他线程尝试取消对该单元格的引用。
这一切似乎都很好(除非我确定肯定会在进行非原子更新之前发生计数的增加,否则我不是很肯定)... 例外一件事:realloc()
通常会更改地址数组,然后further释放旧的数组,指向该数组的指针对其他线程仍然可见。
[确定,所以我不是realloc()
,而是我malloc()
一个新的数组,手动复制内容,将指针设置为该数组。我会释放旧的数组,但是我意识到其他线程可能仍在访问它:它们读取数组的基数;我释放了基地;第三个线程分配它在那里写其他内容;然后,第一个线程将索引的偏移量添加到基数,并期望一个有效的指针。我很乐意泄漏这些。 (鉴于增长一倍,所有合并的旧数组的大小与当前数组的大小大致相同,因此每个项目的开销只是额外的16个字节,而且很快便不再引用它的内存。)
所以,这就是问题的症结所在:一旦分配了更大的数组,就可以完全安全地使用非原子写入来写入基地址吗?还是尽管进行了十亿次访问测试,但实际上是否必须使它成为atomic <>,从而减慢所有辅助线程的读取速度?
((因为这肯定是环境相关的,所以我们所说的是2012年或以后的Intel,g ++ 4到9,以及2012年或以后的Red Hat。)
我能否将一项关键任务应用程序基于此测试的结果,即100个线程读取一个主线程设置的十亿次指针永远不会流泪?做其他任何潜在的问题...
是的,在x86上对齐的负载是原子的,BUT