多线程上的信号量和临界区问题
Semaphore and Critical Section issue on multiple threads
我的多线程代码有问题,希望有人能帮助我。
我希望在控制台上打印所有文件和文件夹,从作为参数给出的文件夹开始。我将此函数用于枚举:
void enumerate(char* path) {
HANDLE hFind;
WIN32_FIND_DATA data;
char *fullpath = new char[strlen(path) - 1];
strcpy(fullpath, path);
fullpath[strlen(fullpath) - 1] = '[=10=]';
hFind = FindFirstFile(path, &data);
do {
if (hFind != INVALID_HANDLE_VALUE) {
if (strcmp(data.cFileName, ".") != 0 && strcmp(data.cFileName, ".."))
{
EnterCriticalSection(&crit);
queue.push(data.cFileName);
LeaveCriticalSection(&crit);
ReleaseSemaphore(semaphore, 1, NULL);
if (data.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
{
strcat(fullpath, data.cFileName);
strcat(fullpath, "\*");
enumerate(fullpath);
}
}
}
} while (FindNextFile(hFind, &data));
FindClose(hFind);
return;
}
当我找到文件或文件夹时,我想将它添加到全局队列并让我的工作线程将它打印到控制台。我的工作线程函数是:
DWORD WINAPI print_queue(LPVOID param) {
while (1) {
WaitForSingleObject(semaphore, INFINITE);
EnterCriticalSection(&crit);
char *rez = queue.front();
queue.pop();
LeaveCriticalSection(&crit);
if (strcmp(rez, "DONE") == 0)
break;
else
std::cout << rez << std::endl;
}
return 1;
}
在main
中,我初始化了信号量和临界区,这两个变量都是全局声明的:
semaphore = CreateSemaphore(NULL, 0,1, NULL);
InitializeCriticalSection(&crit);
然后创建4个线程:
thread1 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId1);
thread2 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId2);
thread3 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId3);
thread4 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId4);
然后我调用 enumerate()
函数并将字符串发送到队列,这将在到达这些字符串时向我的线程发出停止信号:
for (int p = 0; p<4; p++)
{
EnterCriticalSection(&crit);
queue.push(done);
LeaveCriticalSection(&crit);
ReleaseSemaphore(semaphore, 1, NULL);
}
这 4 个字符串是我线程的停止条件。然后我等待线程:
HANDLE * threadArray = new HANDLE[4];
threadArray[0] = thread1;
threadArray[1] = thread2;
threadArray[2] = thread3;
threadArray[3] = thread4;
WaitForMultipleObjects(4, threadArray, TRUE, INFINITE);
并关闭信号量和临界区:
CloseHandle(semaphore);
DeleteCriticalSection(&crit);
出于某种原因,输出是随机垃圾,我不明白为什么。
这是一个示例输出:
te(L┤(L
┤(L
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠°┐*╧wM3╧weµFC4
╠╠╠╠╠
我的逻辑是在 0 上启动信号量,每当队列上发生操作时进入临界区以保护我的数据,在 enumerate()
函数中增加信号量并在 print_queue()
中减少它.
可能是什么问题?
enumerate()
有 许多 个问题:
你没有正确使用 strcpy()
和 strcat()
,所以你在浪费内存。您没有分配足够的内存来保存 strcpy()
的结果,它会复制字符直到到达空终止符。您正在为比需要少 2 个字符(路径中的最后一个字符和空终止符)分配内存。您应该分配 strlen+1
个字符而不是 strlen-1
个字符。更糟糕的是,您使用 strcat()
将文件名连接到分配的字符串上,而没有先重新分配字符串以为文件名腾出空间。
您正在泄漏分配的字符串,因为您从未为它调用 delete[]
。
检查strcmp("..")
时,循环内的if
丢失了!= 0
。
您正在将指针推入 queue
以指向 enumerate()
的本地数据,并在每次循环迭代时被覆盖,并在 enumerate()
时超出范围出口。您的线程需要指向稳定且不会在其背后消失的数据的指针。这是垃圾输出的根源。认为自己很幸运,您的代码只是输出垃圾,而不是直接崩溃。
您没有正确测试 data.dwFileAttributes
字段。您需要使用 &
(按位与)运算符而不是 ==
(等于)运算符。文件夹和文件可以有多个属性,但您只对检查其中一个感兴趣,因此您必须自己测试特定位并忽略其余部分。
您真的应该使用 std::string
来代替字符串管理,让它为您处理内存分配。
此外,考虑使用 std::filesystem
或 boost::filesystem
来处理枚举。
此外,无需在枚举后将 "DONE"
个字符串推入队列。当一个线程收到信号并去提取一个字符串并看到队列为空时,就退出线程。
尝试更像这样的东西:
#include <windows.h>
#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <conditional_variable>
std::queue<std::string> paths;
std::mutex mtx;
std::conditional_variable cv;
bool done = false;
void enumerate(const std::string &path)
{
std::string searchPath = path;
if ((!searchPath.empty()) && (searchPath[searchPath.length()-1] != '\'))
searchPath += '\';
WIN32_FIND_DATA data;
HANDLE hFind = FindFirstFileA((searchPath + "*").c_str(), &data);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if ((strcmp(data.cFileName, ".") != 0) && (strcmp(data.cFileName, "..") != 0))
{
string fullpath = searchPath + data.cFileName;
{
std::lock_guard<std::mutex> lock(mtx);
paths.push(fullpath);
cv.notify_one();
}
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
enumerate(fullpath);
}
}
while (FindNextFileA(hFind, &data));
FindClose(hFind);
}
}
void print_queue()
{
std::unique_lock<std::mutex> lock(mtx);
while (true)
{
cv.wait(lock, [](){ return (!paths.empty()) || done; });
if (paths.empty())
return;
std::string rez = paths.front();
paths.pop();
std::cout << rez << std::endl;
}
}
int main()
{
std::thread thread1(print_queue);
std::thread thread2(print_queue);
std::thread thread3(print_queue);
std::thread thread4(print_queue);
enumerate("C:\");
done = true;
cv.notify_all();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
return 0;
}
你没有写出你使用的是哪种队列,但我猜是queue<char*>
。这意味着它只存储指向其他地方拥有的内存的指针。
当您现在执行 queue.push(data.cFileName);
时,您写入了一个指向队列的指针,该指针在下一次迭代后无效,因为 data
在那里发生了变化。在 enumerate
存在之后,数据指针(以及队列元素)甚至会指向未定义的内存,这将解释输出。
修复队列中文件名的存储副本,例如通过使用 queue<std::string>
我的多线程代码有问题,希望有人能帮助我。
我希望在控制台上打印所有文件和文件夹,从作为参数给出的文件夹开始。我将此函数用于枚举:
void enumerate(char* path) {
HANDLE hFind;
WIN32_FIND_DATA data;
char *fullpath = new char[strlen(path) - 1];
strcpy(fullpath, path);
fullpath[strlen(fullpath) - 1] = '[=10=]';
hFind = FindFirstFile(path, &data);
do {
if (hFind != INVALID_HANDLE_VALUE) {
if (strcmp(data.cFileName, ".") != 0 && strcmp(data.cFileName, ".."))
{
EnterCriticalSection(&crit);
queue.push(data.cFileName);
LeaveCriticalSection(&crit);
ReleaseSemaphore(semaphore, 1, NULL);
if (data.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
{
strcat(fullpath, data.cFileName);
strcat(fullpath, "\*");
enumerate(fullpath);
}
}
}
} while (FindNextFile(hFind, &data));
FindClose(hFind);
return;
}
当我找到文件或文件夹时,我想将它添加到全局队列并让我的工作线程将它打印到控制台。我的工作线程函数是:
DWORD WINAPI print_queue(LPVOID param) {
while (1) {
WaitForSingleObject(semaphore, INFINITE);
EnterCriticalSection(&crit);
char *rez = queue.front();
queue.pop();
LeaveCriticalSection(&crit);
if (strcmp(rez, "DONE") == 0)
break;
else
std::cout << rez << std::endl;
}
return 1;
}
在main
中,我初始化了信号量和临界区,这两个变量都是全局声明的:
semaphore = CreateSemaphore(NULL, 0,1, NULL);
InitializeCriticalSection(&crit);
然后创建4个线程:
thread1 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId1);
thread2 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId2);
thread3 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId3);
thread4 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId4);
然后我调用 enumerate()
函数并将字符串发送到队列,这将在到达这些字符串时向我的线程发出停止信号:
for (int p = 0; p<4; p++)
{
EnterCriticalSection(&crit);
queue.push(done);
LeaveCriticalSection(&crit);
ReleaseSemaphore(semaphore, 1, NULL);
}
这 4 个字符串是我线程的停止条件。然后我等待线程:
HANDLE * threadArray = new HANDLE[4];
threadArray[0] = thread1;
threadArray[1] = thread2;
threadArray[2] = thread3;
threadArray[3] = thread4;
WaitForMultipleObjects(4, threadArray, TRUE, INFINITE);
并关闭信号量和临界区:
CloseHandle(semaphore);
DeleteCriticalSection(&crit);
出于某种原因,输出是随机垃圾,我不明白为什么。
这是一个示例输出:
te(L┤(L ┤(L ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠°┐*╧wM3╧weµFC4 ╠╠╠╠╠
我的逻辑是在 0 上启动信号量,每当队列上发生操作时进入临界区以保护我的数据,在 enumerate()
函数中增加信号量并在 print_queue()
中减少它.
可能是什么问题?
enumerate()
有 许多 个问题:
你没有正确使用
strcpy()
和strcat()
,所以你在浪费内存。您没有分配足够的内存来保存strcpy()
的结果,它会复制字符直到到达空终止符。您正在为比需要少 2 个字符(路径中的最后一个字符和空终止符)分配内存。您应该分配strlen+1
个字符而不是strlen-1
个字符。更糟糕的是,您使用strcat()
将文件名连接到分配的字符串上,而没有先重新分配字符串以为文件名腾出空间。您正在泄漏分配的字符串,因为您从未为它调用
delete[]
。检查
strcmp("..")
时,循环内的if
丢失了!= 0
。您正在将指针推入
queue
以指向enumerate()
的本地数据,并在每次循环迭代时被覆盖,并在enumerate()
时超出范围出口。您的线程需要指向稳定且不会在其背后消失的数据的指针。这是垃圾输出的根源。认为自己很幸运,您的代码只是输出垃圾,而不是直接崩溃。您没有正确测试
data.dwFileAttributes
字段。您需要使用&
(按位与)运算符而不是==
(等于)运算符。文件夹和文件可以有多个属性,但您只对检查其中一个感兴趣,因此您必须自己测试特定位并忽略其余部分。
您真的应该使用 std::string
来代替字符串管理,让它为您处理内存分配。
此外,考虑使用 std::filesystem
或 boost::filesystem
来处理枚举。
此外,无需在枚举后将 "DONE"
个字符串推入队列。当一个线程收到信号并去提取一个字符串并看到队列为空时,就退出线程。
尝试更像这样的东西:
#include <windows.h>
#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <conditional_variable>
std::queue<std::string> paths;
std::mutex mtx;
std::conditional_variable cv;
bool done = false;
void enumerate(const std::string &path)
{
std::string searchPath = path;
if ((!searchPath.empty()) && (searchPath[searchPath.length()-1] != '\'))
searchPath += '\';
WIN32_FIND_DATA data;
HANDLE hFind = FindFirstFileA((searchPath + "*").c_str(), &data);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if ((strcmp(data.cFileName, ".") != 0) && (strcmp(data.cFileName, "..") != 0))
{
string fullpath = searchPath + data.cFileName;
{
std::lock_guard<std::mutex> lock(mtx);
paths.push(fullpath);
cv.notify_one();
}
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
enumerate(fullpath);
}
}
while (FindNextFileA(hFind, &data));
FindClose(hFind);
}
}
void print_queue()
{
std::unique_lock<std::mutex> lock(mtx);
while (true)
{
cv.wait(lock, [](){ return (!paths.empty()) || done; });
if (paths.empty())
return;
std::string rez = paths.front();
paths.pop();
std::cout << rez << std::endl;
}
}
int main()
{
std::thread thread1(print_queue);
std::thread thread2(print_queue);
std::thread thread3(print_queue);
std::thread thread4(print_queue);
enumerate("C:\");
done = true;
cv.notify_all();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
return 0;
}
你没有写出你使用的是哪种队列,但我猜是queue<char*>
。这意味着它只存储指向其他地方拥有的内存的指针。
当您现在执行 queue.push(data.cFileName);
时,您写入了一个指向队列的指针,该指针在下一次迭代后无效,因为 data
在那里发生了变化。在 enumerate
存在之后,数据指针(以及队列元素)甚至会指向未定义的内存,这将解释输出。
修复队列中文件名的存储副本,例如通过使用 queue<std::string>