Jam*_*mes 4 c++ windows multithreading
我不太擅长多线程编程,所以我想寻求一些帮助/建议。
在我的应用程序中,我有两个线程试图访问共享对象。可以考虑尝试从另一个对象中调用函数的两个任务。为清楚起见,我将展示程序的某些部分,这些部分可能不是很相关,但希望可以帮助解决我的问题。
请看下面的示例代码:
//DataLinkLayer.h
class DataLinkLayer: public iDataLinkLayer {
public:
DataLinkLayer(void);
~DataLinkLayer(void);
};
Run Code Online (Sandbox Code Playgroud)
其中 iDataLinkLayer 是一个接口(没有任何实现的抽象类),包含纯虚函数和一个指向 DataLinkLayer 对象(dataLinkLayer)的引用(指针)声明。
// DataLinkLayer.cpp
#include "DataLinkLayer.h"
DataLinkLayer::DataLinkLayer(void) {
/* In reality task constructors takes bunch of other parameters
but they are not relevant (I believe) at this stage. */
dll_task_1* task1 = new dll_task_1(this);
dll_task_2* task2 = new dll_task_2(this);
/* Start multithreading */
task1->start(); // task1 extends thread class
task2->start(); // task2 also extends thread class
}
/* sample stub functions for testing */
void DataLinkLayer::from_task_1() {
printf("Test data Task 1");
}
void DataLinkLayer::from_task_2() {
printf("Test data Task 2");
}
Run Code Online (Sandbox Code Playgroud)
任务 1 的实现如下。dataLinLayer 接口 (iDataLinkLayer) 指针被传递给类构造函数,以便能够从 dataLinkLayer 实例中访问必要的函数。
//data_task_1.cpp
#include "iDataLinkLayer.h" // interface to DataLinkLayer
#include "data_task_1.h"
dll_task_1::dll_task_1(iDataLinkLayer* pDataLinkLayer) {
this->dataLinkLayer = pDataLinkLayer; // dataLinkLayer declared in dll_task_1.h
}
// Run method - executes the thread
void dll_task_1::run() {
// program reaches this point and prints the stuff
this->datalinkLayer->from_task_1();
}
// more stuff following - not relevant to the problem
...
Run Code Online (Sandbox Code Playgroud)
任务 2 看起来类似:
//data_task_2.cpp
#include "iDataLinkLayer.h" // interface to DataLinkLayer
#include "data_task_2.h"
dll_task_2::dll_task_2(iDataLinkLayer* pDataLinkLayer){
this->dataLinkLayer = pDataLinkLayer; // dataLinkLayer declared in dll_task_2.h
}
// // Run method - executes the thread
void dll_task_2::run() {
// ERROR: 'Access violation reading location 0xcdcdcdd9' is signalled at this point
this->datalinkLayer->from_task_2();
}
// more stuff following - not relevant to the problem
...
Run Code Online (Sandbox Code Playgroud)
因此,据我所知,我从两个不同的线程(任务)访问共享指针,这是不允许的。坦率地说,我认为我仍然可以访问该对象,但结果可能出乎意料。
当 dll_task_2 尝试使用指向 DataLinkLayer 的指针调用该函数时,似乎出现了严重错误。dll_task_2 的优先级较低,因此它在之后启动。我不明白为什么我仍然无法至少访问对象......我可以使用互斥锁来锁定变量,但我认为这样做的主要原因是为了保护变量/对象。
我正在使用 Microsoft Visual C++ 2010 Express。我对多线程了解不多,所以也许您可以为这个问题提出更好的解决方案并解释问题的原因。
访问冲突的地址是一个非常小的正偏移量 0xcdcdcdcd
维基百科说:
CDCDCDCD 被微软的 C++ 调试运行库用来标记未初始化的堆内存
free 后对应的值为 0xdddddddd,所以很可能是初始化不完整而不是 use-after-free 。
编辑:詹姆斯问优化如何弄乱虚函数调用。基本上,这是因为当前标准化的 C++ 内存模型不保证线程。C++ 标准定义从构造函数内部进行的虚拟调用将使用当前正在运行的构造函数的声明类型,而不是对象的最终动态类型。所以这意味着,从C++顺序执行内存模型的角度来看,必须在构造函数开始运行之前设置虚调用机制(实际上就是一个v-table指针)(我相信具体点是在基子对象构造之后在ctor-initializer-list和成员子对象构造之前)。
现在,在线程场景中,可能发生两件事使可观察行为不同:
首先,编译器可以自由地执行任何优化,在 C++ 顺序执行模型中,就像遵循规则一样。例如,如果编译器可以证明在构造函数内部没有进行虚拟调用,它可以等待并在构造函数体的末尾而不是开头设置 v-table 指针。如果构造函数没有给出this指针,因为构造函数的调用者还没有收到它的指针副本,那么构造函数调用的任何函数都不能回调(虚拟或静态)下的对象建造。但是构造函数确实放弃了this指针。
我们必须仔细观察。如果给出 this 指针的函数对编译器是可见的(即包含在当前编译单元中),则编译器可以在分析中包含它的行为。在这个问题中我们没有给出那个函数( 的构造函数和成员函数class task),但似乎唯一发生的事情是所述指针存储在一个子对象中,该子对象也无法从构造函数外部访问。
“犯规!”,你哭了,“我把那个task子对象的地址传递给了一个库CreateThread函数,因此它是可达的,通过它,主对象是可达的。” 啊,但是你没有理解“严格别名规则”的奥秘。那个库函数不接受 type 的参数task *,现在是吗?并且作为一个参数,其类型可能是intptr_t,但绝对不是task *也不是char *,编译器被允许假设,为了 as-if 优化的目的,它不指向一个task对象(即使它显然指向)。如果它不指向一个task对象,并且我们的this指针存储的唯一位置是在task成员子对象中,那么它不能用于对this,因此编译器可以合理地延迟设置虚拟调用机制。
但这还不是全部。即使编译器确实按计划设置了虚拟调用机制,CPU 内存模型也只保证更改对当前 CPU 内核可见。写入可能会以完全不同的顺序对其他 CPU 内核可见。现在,库创建线程函数应该引入一个内存屏障来限制 CPU 写入重新排序,但事实上 Koz 的回答引入了一个临界区(其中肯定包括一个内存屏障)改变了行为表明可能没有内存屏障存在于原始代码。
并且,CPU 写重排序不仅可以延迟 v-table 指针,还可以延迟 this 指针到task子对象中的存储。
我希望你喜欢这个关于“多线程编程很难”洞穴小角落的导览游。
| 归档时间: |
|
| 查看次数: |
6535 次 |
| 最近记录: |