多线程上的信号量和关键部分问题

Ryn*_*ops 0 c++ winapi multithreading semaphore critical-section

我的多线程代码存在问题,希望有人可以帮助我.

我希望在控制台上打印所有文件和文件夹,从作为参数给出的文件夹开始.我使用此函数进行枚举:

void enumerate(char* path) {
    HANDLE hFind;
    WIN32_FIND_DATA data;

    char *fullpath = new char[strlen(path) - 1];
    strcpy(fullpath, path);
    fullpath[strlen(fullpath) - 1] = '\0';

    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;
}
Run Code Online (Sandbox Code Playgroud)

当我找到文件或文件夹时,我想将其添加到全局队列中,并让我的工作线程将其打印到控制台.我的工作线程功能是:

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;
}
Run Code Online (Sandbox Code Playgroud)

main,我初始化信号量和关键部分,这两个变量全局声明:

semaphore = CreateSemaphore(NULL, 0,1, NULL);
InitializeCriticalSection(&crit);
Run Code Online (Sandbox Code Playgroud)

然后创建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);
Run Code Online (Sandbox Code Playgroud)

然后我调用该enumerate()函数并将字符串调用到队列中,这将指示我的线程在到达这些字符串时停止:

for (int p = 0; p<4; p++)
{
    EnterCriticalSection(&crit);
    queue.push(done);
    LeaveCriticalSection(&crit);
    ReleaseSemaphore(semaphore, 1, NULL);
}
Run Code Online (Sandbox Code Playgroud)

那4个字符串是我的线程的停止条件.然后我等待线程:

HANDLE * threadArray = new HANDLE[4];

threadArray[0] = thread1;
threadArray[1] = thread2;
threadArray[2] = thread3;
threadArray[3] = thread4;

WaitForMultipleObjects(4, threadArray, TRUE, INFINITE);
Run Code Online (Sandbox Code Playgroud)

并关闭信号量和关键部分:

CloseHandle(semaphore);
DeleteCriticalSection(&crit);
Run Code Online (Sandbox Code Playgroud)

由于某种原因,输出是随机垃圾,我无法弄清楚为什么.

这是一个示例输出:

te(L?(L
?(L 
 ??????????????????????????????????????
????????????????????????????????????????
??????????????????????????????????????????°?*?wM3?weµFC4
?????

我的逻辑是在0上启动信号量,每当队列上发生操作时进入临界区以保护我的数据,增加enumerate()函数中的信号量并减少它print_queue().

可能是什么问题?

Rem*_*eau 5

enumerate()MANY问题:

  • 你不使用strcpy()strcat()正确的,所以你捣毁内存.您没有分配足够的内存来保存结果strcpy(),该结果复制字符直到它到达空终止符.您分配的内存比所需的字符少2个(路径中的最后一个字符,以及空终止符).你应该分配strlen+1字符而不是strlen-1字符.更糟糕的是,您正在使用strcat()将文件名连接到分配的字符串而不首先重新分配字符串以为文件名腾出空间.

  • 你正在泄漏分配的字符串,因为你从来没有要求delete[]它.

  • if循环内部缺少!= 0检查时strcmp("..").

  • 您正在将指针推送到queue本地数据enumerate()并在每次循环迭代时被覆盖,并在enumerate()退出时超出范围.您的线程期望指向稳定且不会在其背后消失的数据.这是垃圾输出的根源.认为自己很幸运,你的代码只是输出垃圾,而不仅仅是彻底崩溃.

  • 您没有data.dwFileAttributes正确测试该字段.您需要使用&(按位AND)运算符而不是==(等于)运算符.文件夹和文件可以有多个属性,但您只对检查一个属性感兴趣,因此您必须自己测试该特定位并忽略其余位.

你真的应该使用它std::string来进行字符串管理,并让它为你处理内存分配.

另外,请考虑使用std::filesystemboost::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;
}
Run Code Online (Sandbox Code Playgroud)