我在多线程编程方面不是很擅长,所以我想请求一些帮助/建议。
在我的应用程序中,我有两个线程试图访问共享对象。 可以考虑尝试从另一个对象中调用函数的两个任务。为清楚起见,我将展示该计划的某些部分可能不太相关,但希望可以帮助我更好地解决问题。
请查看以下示例代码:
//DataLinkLayer.h
class DataLinkLayer: public iDataLinkLayer {
public:
DataLinkLayer(void);
~DataLinkLayer(void);
};
其中iDataLinkLayer是一个接口(没有任何实现的抽象类),包含纯虚函数和对DataLinkLayer对象(dataLinkLayer)的isntance的引用(指针)声明。
// 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");
}
任务1的实施如下。 dataLinLayer接口(iDataLinkLayer)指针被传递给类cosntructor,以便能够从dataLinkLayer isntance中访问必要的函数。
//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
...
任务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
...
据我所知,我从两个不同的线程(任务)访问共享指针,但是不允许这样做。 坦率地说,我认为我将能够访问该对象,但结果可能是意外的。
当dll_task_2尝试使用指向DataLinkLayer的指针调用函数时,似乎出现了严重错误。 dll_task_2具有较低的优先级,因此它在之后启动。我不明白为什么我仍然不能至少访问该对象... 我可以使用互斥锁来锁定变量,但我认为这样做的主要原因是保护变量/对象。
我正在使用Microsoft Visual C ++ 2010 Express。 我对多线程知之甚少,所以也许你可以建议更好地解决这个问题,并解释问题的原因。
答案 0 :(得分:4)
访问冲突的地址是一个非常小的正偏移,来自0xcdcdcdcd
CDCDCDCD由Microsoft的C ++调试运行时库用于标记未初始化的堆内存
Here is the relevant MSDN page
The corresponding value after free is 0xdddddddd,因此可能是初始化不完整而不是免费使用。
编辑:詹姆斯询问优化如何搞乱虚拟函数调用。基本上,这是因为当前标准化的C ++内存模型不保证线程。 C ++标准定义了在构造函数中进行的虚拟调用将使用当前正在运行的构造函数的声明类型,而不是对象的最终动态类型。所以这意味着,从C ++顺序执行内存模型的角度来看,必须在构造函数开始运行之前建立虚拟调用机制(实际上,一个v表指针)(我相信具体点是在基础子对象构造之后)在 ctor-initializer-list 和成员子对象构造之前)。
现在,有两件事可能会使线程场景中的可观察行为不同:
首先,编译器可以自由地执行任何优化,在C ++顺序执行模型中,如果遵循规则,这些优化将起作用。例如,如果编译器可以证明在构造函数中没有进行虚拟调用,它可以等待并在构造函数体的末尾而不是开头设置v表指针。如果构造函数没有给出this
指针,因为构造函数的调用者还没有收到指针的副本,那么构造函数调用的函数都不能回调(虚拟或静态) )到正在建设的对象。但构造函数DOES会放弃this
指针。
我们必须仔细观察。如果给出该指针的函数对于编译器是可见的(即包括在当前编译单元中),则编译器可以在分析中包括其行为。我们没有在这个问题中给出该函数(class task
的构造函数和成员函数),但似乎唯一发生的事情是所述指针存储在一个子对象中,该子对象也无法从外部访问构造函数。
“犯规!”,你哭了,“我把那个task
子对象的地址传递给了一个库CreateThread
函数,因此它是可以访问的,通过它,主对象是可以访问的。”啊,但你不理解“严格别名规则”的奥秘。那个库函数不接受task *
类型的参数,现在呢?作为一个类型可能为intptr_t
的参数,但绝对不是task *
也不是char *
的参数,为了优化原因,允许编译器假定它不指向一个task
对象(即使它显然也是如此)。如果它没有指向task
对象,并且我们的this
指针存储的唯一位置在task
成员子对象中,则它不能用于对{this
成员进行虚拟调用{1}},因此编译器可能合法地延迟设置虚拟呼叫机制。
但这不是全部。即使编译器确实按计划设置了虚拟调用机制,CPU内存模型也只能保证当前CPU内核可以看到更改。写入可能以完全不同的顺序对其他CPU核心可见。现在,库创建线程函数应该引入一个限制CPU写入重新排序的内存屏障,但是Koz的答案引入一个关键部分(肯定包含一个内存屏障)改变了行为,这表明可能没有内存屏障存在。原始代码。
并且,CPU写入重新排序不仅可以延迟v表指针,还可以将此指针存储到task
子对象中。
我希望你喜欢这次“多线程编程很难”的洞穴小角落之旅。
答案 1 :(得分:1)
printf不是,afaik,线程安全。尝试使用关键部分围绕printf。
要在iDataLinkLayer类中执行此操作InitializeCriticalSection。然后在printfs周围,您需要EnterCriticalSection和LeaveCriticalSection。这将阻止两个函数同时进入printf。
修改:尝试更改此代码:
dll_task_1* task1 = new task(this);
dll_task_2* task2 = new task(this);
到
dll_task_1* task1 = new dll_task_1(this);
dll_task_2* task2 = new dll_task_2(this);
我猜这个任务实际上是dll_task_1和dll_task_2的基类......所以,最重要的是,我很惊讶它编译....
答案 2 :(得分:0)
我认为在构造函数结束之前使用'this'(即调用成员函数)并不总是安全的。可能是该任务在DataLinkLayer构造函数结束之前调用DataLinkLayer的成员函数。特别是如果此成员函数是虚拟的: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.7
答案 3 :(得分:0)
我想评论一下DataLinkLayer的创建。
当我从main调用DataLinkLayer构造函数时:
int main () {
DataLinkLayer* dataLinkLayer = new DataLinkLayer();
while(true); // to keep the main thread running
}
我,腐败,不破坏对象,这是第一个。现在,在DataLinkLayer cosntructor中,我初始化了许多(不仅仅是这两个任务)其他对象的isntances并传递给大多数dataLinkLayer指针(使用this
)。就我而言,这是合法的。更进一步 - 它按预期编译和运行。
我对此感到好奇的是我所遵循的整体设计理念(如果有的话:)。
DataLinkLayer是一个父类,由几个尝试修改参数或执行其他处理的任务访问。因为我希望一切都尽可能地解耦,所以我只为访问器提供接口并封装数据,这样我就没有任何全局变量,友元函数等。
如果只有多线程不存在,那将是一项非常容易的任务。我相信我会在途中遇到许多其他陷阱。
欢迎您和merci进行讨论,以获取您的慷慨评论!
<强> UPD:强>
说到将iDataLinkLayer接口指针传递给任务 - 这是一个很好的方法吗?在Java中,实现一个包含或所谓的策略模式以使事物分离和填充是很平常的事情。但是我不能100%确定它是否是c ++中的一个很好的解决方案......有什么建议/通信吗?