如何与不同编译单元中的线程安全地共享变量?
在我的程序结构中,我将“从哪里调用它”和“完成什么”分成单独的源文件。从实用性角度来看,这允许我将程序编译为独立的或将其包含在 DLL 中。下面的代码不是实际的代码,而是一个简化的示例,表明了同样的观点。
这里有 3 个交互组件:加载我的 DLL 的内核模式程序、DLL 及其源文件以及单独维护的实用程序及其源代码。
在DLL形式中,程序作为线程加载。根据内核模式应用程序供应商的文档,我在内核程序初始化后失去了调用 Win32 API 函数的能力,因此我将线程加载为活动线程(而不是使用 CREATE_SUSPENDED,因为我无法唤醒它)。
我让它监视一个标志变量,以便它知道何时通过一个不优雅但实用的方法做一些有用的事情:
while ( pauseThreadFlag ) Sleep(1000);
最多 1 秒的延迟是可以接受的(整个过程很长,并且很少被调用)并且似乎不会影响系统。
在线程源文件中,我将变量声明为
volatile bool pauseThreadFlag = true;
在我声明的 DLL 源文件中
extern volatile bool pauseThreadFlag;
,当我准备好让线程执行时,在我设置的 DLL 中,
pauseThreadFlag = false;
我在将 std::string 对象声明为易失性时遇到了一些困难,因此我将参数声明为线程源文件中的全局变量,并让 DLL 调用设置器驻留在线程源文件中。如果我可以随意实例化线程,这些字符串将是参数。
(所有这一切都缺少锁定变量以保证线程安全,这是我的下一个“要做的事情”)
这让我觉得这是一个糟糕的设计……它很实用,但很复杂。考虑到我提到的限制,是否有更好的方法来解决这个问题?
我认为一个可能的修改是使用线程创建时给出的 LPVOID lpParams 变量来保存指向字符串对象的指针,即使在创建线程时字符串将为空,并直接从线程访问它们,这样完全删除线程程序中的声明、设置器等?如果这有效,那么也可以在那里引用暂停标志,并且消除外部声明(但我认为仍然需要将其声明为 volatile 以提示优化器)。
如果有什么区别的话,环境是 Visual Studio 2010,C++,目标平台 Win32 (XP)。
谢谢!
In the structure of my program I've divided "where it gets called from" and "what gets done" into separate source files. As a matter of practicality, this allows me to compile the program as standalone or include it in a DLL. The code below is not the actual code but a simplified example that makes the same point.
There are 3 interacting components here: kernel mode program that loads my DLL, the DLL and its source files and the utility program with it's source, that is maintained separately.
In the DLL form, the program is loaded as a thread. According to the kernel mode application vendor's documentation, I loose the ability to call Win32 API functions after the initialization of the kernel program so I load the thread as an active thread (as opposed to using CREATE_SUSPENDED since I can't wake it).
I have it monitor a flag variable so that it knows when to do something useful through an inelegant but functional:
while ( pauseThreadFlag ) Sleep(1000);
The up to 1 second lag is acceptable (the overall process is lengthy, and infrequently called) and doesn't seem to impact the system.
In the thread source file I declare the variable as
volatile bool pauseThreadFlag = true;
Within the DLL source file I've declared
extern volatile bool pauseThreadFlag;
and when I am ready to have the thread execute, in the DLL I set
pauseThreadFlag = false;
I've had some difficulty in declaring std::string objects as volatile, so instead I have declared my parameters as global variables within the thread's source file and have the DLL call setters which reside in the thread's source. These strings would have been parameters if I could instantiate the thread at will.
(Missing from all of this is locking the variable for thread safety, which is my next "to do")
This strikes me as a bad design ... it's functional but convoluted. Given the constraints that I've mentioned is there a better way to go about this?
I was thinking that a possible revision would be to use the LPVOID lpParams variable given at thread creation to hold pointers to the string objects, even though the strings will be empty when the thread is created, and access them directly from the thread, that way erasing the declarations, setters, etc in the thread program altogether? If this works then the pause flag could also be referenced there, and the extern declarations eliminated (but I think it still needs to be declared volatile to hint the optimizer).
If it makes any difference, the environment is Visual Studio 2010, C++, target platform Win32 (XP).
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果所有组件都在内核模式下运行,您需要查看 KeInitializeEvent, KeSetEvent,KeResetEvent和 KeWaitForSingleObject。这些都以与其用户模式等效的方式类似的方式工作。
If all components are running in kernel mode you will want to take a look at KeInitializeEvent, KeSetEvent, KeResetEvent and KeWaitForSingleObject. These all work in a similar fashion to their user mode equivalents.
我最终删除了该结构并将其替换为封装所有数据的对象。它有点丑陋,充满了 getter 和 setter,但在这种特殊情况下,我使用访问方法来确保正确设置/取消设置锁。
使用指向该对象的 void 强制转换指针正确地传递了该对象,并且看起来相当稳定。
I ended up removing the struct and replacing it with an object that encapsulates all the data. It's a little hideous, being filled with getters and setters, but in this particular case I'm using the access methods to make sure that locks are properly set/unset.
Using a void cast pointer to this object passed the object correctly and it seems quite stable.