代码之家  ›  专栏  ›  技术社区  ›  Ryncops

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

  •  0
  • Ryncops  · 技术社区  · 7 年前

    我的多线程代码有问题,希望有人能帮我解决。

    我希望在控制台上打印从作为参数给定的文件夹开始的所有文件和文件夹。我将此函数用于枚举:

    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;
    }
    

    当我找到一个文件或文件夹时,我想将其添加到全局队列,并让我的工作线程将其打印到控制台。我的工作线程函数是:

    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上启动信号量,每当队列上发生操作时,就进入关键部分以保护我的数据,在 枚举() 功能并将其减少 print_queue()

    可能有什么问题?

    2 回复  |  直到 7 年前
        1
  •  5
  •   Remy Lebeau    7 年前

    enumerate() 许多 问题:

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

    • 您正在泄漏分配的字符串,因为您从未调用 delete[] 为了它。

    • 这个 if 回路内部缺失 != 0 检查时 strcmp("..")

    • 您正在将指针推入 queue 到本地的数据 枚举() 并在每次循环迭代时被覆盖,并且在 枚举() 退出。您的线程期望指向稳定且不会在背后消失的数据的指针。这是垃圾输出的根。幸运的是,您的代码只是输出垃圾,而不是直接崩溃。

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

    你真的应该使用 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;
    }
    
        2
  •  1
  •   Matthias247    7 年前

    你没有写下你使用哪种队列,但我想这是一个 queue<char*> 。这意味着它只存储指向其他地方拥有的内存的指针。

    当你现在这样做的时候 queue.push(data.cFileName); 您编写了一个指向队列的指针,该指针在下一次迭代后无效,因为 data 这里有变化。之后 enumerate 数据指针(以及队列元素)甚至会指向未定义的内存,这将解释输出。

    要修复此存储队列中文件名的副本,例如使用 queue<std::string>