0

I am a beginner with threads and am trying to write code to extract 20 tags from a file. The number of files can run up to 7000, so I would like to make good use of a thread-pool.

I use Code::Blocks 20.3 and MinGW 17.1 on a Windows 10 Pro computer.

I have 'borrowed' the thread-pool code from: https://codereview.stackexchange.com/questions/221626/c17-thread-pool

I made a test that MinGW probably handles as C code, and that worked just fine.

My project involves multiple class files with dialog windows, and when I copied the working C code it fails to build. Unfortunately I do not understand how to convert the code from C to C++.

The test code I wrote is below.

The build messages are:

||=== Build: Debug in ThreadPool2 (compiler: GNU GCC Compiler) ===|
Threadpool.h||In instantiation of 'auto Thread_Pool::execute(F, Args&& ...) [with F = TrackTags::TagsStdStrings (TrackTags::*)(std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>); Args = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&}]':|
TrackTags.cpp|43|required from here|
Threadpool.h|62|error: no type named 'type' in 'struct std::invoke_result<TrackTags::TagsStdStrings (TrackTags::*)(std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>'|
Threadpool.h|63|error: no type named 'type' in 'struct std::invoke_result<TrackTags::TagsStdStrings (TrackTags::*)(std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>'|
Threadpool.h|62|error: no type named 'type' in 'struct std::invoke_result<TrackTags::TagsStdStrings (TrackTags::*)(std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>'|
Threadpool.h|63|error: no type named 'type' in 'struct std::invoke_result<TrackTags::TagsStdStrings (TrackTags::*)(std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>'|
TrackTags.cpp||In member function 'void TrackTags::GetMultiStdTags()':|
TrackTags.cpp|43|error: invalid use of void expression|
||=== Build failed: 5 error(s), 2 warning(s) (0 minute(s), 0 second(s)) ===|

the lines with errors are:

TrackTags.cpp

[43]  StdFutures.push_back(Pool.execute(GetStdTags, wsFile, wsCol));

Threadpool.h

[62]  std::packaged_task<std::invoke_result_t<F, Args...>()> Task_PKG(std::bind(function, args...) );
[63]  std::future<std::invoke_result_t<F, Args...>> Future = Task_PKG.get_future();

in Threadpool.h.

I tried:

[43] StdFutures.push_back(Pool.execute(std::mem_fn(TrackTags::GetStdTags), std::ref(wsFile), std::ref(wsCol)));

But this did not help.

I hope someone can help me make this work. Thank you.

Ruud.

---TrackTags.h---

#ifndef TRACKTAGS_H
#define TRACKTAGS_H

#include "Threadpool.h"

#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#include <vector>

class TrackTags
{
    public:
        struct TagsStdStrings
        {
            bool OK;
            std::string ThreadID;
            std::string FileName;
            std::string Collection;
        };

    public:
        TrackTags();
        virtual ~TrackTags();

        TagsStdStrings GetStdTags(std::string wsFile, std::string wsCollection);
        void GetMultiStdTags();

    protected:

    private:
};

#endif // TRACKTAGS_H

---TrackTags.cpp---

#include "TrackTags.h"

#define _UNICODE

TrackTags::TrackTags()
{
    //ctor
}

TrackTags::~TrackTags()
{
    //dtor
}

TrackTags::TagsStdStrings TrackTags::GetStdTags(std::string wsFile, std::string wsCollection)
{
    TagsStdStrings TagLine;
    TagLine.FileName = wsFile;
    TagLine.Collection = wsCollection;
    TagLine.OK = true;
    // Add thread-ID to the structure
    auto tid = std::this_thread::get_id();
    std::stringstream ssID;
    ssID << tid;
    std::string sID{ssID.str()};
    TagLine.ThreadID = sID;
    return TagLine;
}

void TrackTags::GetMultiStdTags()

{
    Thread_Pool Pool(1);
    std::vector<std::future<TagsStdStrings>> StdFutures;
    std::string wsFile{"FileTest"};
    std::string wsCol{"ColTest"};
    StdFutures.push_back(Pool.execute(GetStdTags, wsFile, wsCol));
    for (auto &Fut : StdFutures)
    {
        TagsStdStrings TSS;
        TSS = Fut.get();
        if (TSS.OK)
        { std::cout << TSS.ThreadID << "--" << TSS.FileName << "--" << TSS.Collection << std::endl; }
        else
        { std::cout << "Empty Tag structure\n"; }
    }
}

---Threadpool.h---

#pragma once
#include <condition_variable>
#include <functional> //bind
#include <future> //packaged_task
#include <mutex>
#include <queue>
#include <thread>
#include <type_traits> //invoke_result
#include <vector>
class Thread_Pool
{
    public:
        Thread_Pool(size_t Thread_Count);
        ~Thread_Pool();
        Thread_Pool(const Thread_Pool &) = delete;
        Thread_Pool &operator=(const Thread_Pool &) = delete;
        template <typename F, typename ...Args>
        auto execute(F, Args&&...);
    private:
        class Task_Container_Base
        {
            public:
                virtual ~Task_Container_Base() {};
                virtual void operator()() = 0;
        };
        template <typename F>
        class Task_Container : public Task_Container_Base
        {
            public:
                Task_Container(F &&Fnc) : f(std::forward<F>(Fnc)) {}
                void operator()() override { f(); }
            private:
                F f;
        };
        template <typename Func>
        static std::unique_ptr<Task_Container_Base> Allocate_Task_Container(Func &&f)
        {
            return std::unique_ptr<Task_Container_Base>(new Task_Container<Func>(std::forward<Func>(f))
        );
    }
    std::vector<std::thread> Threads;
    std::queue<std::unique_ptr<Task_Container_Base>> Tasks;
    std::mutex Task_Mutex;
    std::condition_variable Task_CV;
    bool Stop_Threads = false;
};

template <typename F, typename ...Args>
auto Thread_Pool::execute(F function, Args &&...args)
{
    std::unique_lock<std::mutex> Queue_Lock(Task_Mutex, std::defer_lock);
    std::packaged_task<std::invoke_result_t<F, Args...>()> Task_PKG(std::bind(function, args...) );
    std::future<std::invoke_result_t<F, Args...>> Future = Task_PKG.get_future();
    Queue_Lock.lock();
    Tasks.emplace(Allocate_Task_Container( [Task(std::move(Task_PKG))]() mutable { Task(); }) );
    Queue_Lock.unlock();
    Task_CV.notify_one();
    return Future;
}

---Threadpool.cpp---

#include "Threadpool.h"
Thread_Pool::Thread_Pool(size_t Thread_Count)
{
    for (size_t i = 0; i < Thread_Count; ++i)
    {
        Threads.emplace_back( std::thread( [&]()
        {
            std::unique_lock<std::mutex> Queue_Lock(Task_Mutex, std::defer_lock);
            while (true)
            {
                Queue_Lock.lock();
                Task_CV.wait( Queue_Lock, [&]() -> bool { return !Tasks.empty() || Stop_Threads; } );
                if (Stop_Threads && Tasks.empty()) return;
                auto Temp_Task = std::move(Tasks.front());
                Tasks.pop();
                Queue_Lock.unlock();
                (*Temp_Task)();
           }
       } ) );
    }
}

Thread_Pool::~Thread_Pool()
{
    Stop_Threads = true;
    Task_CV.notify_all();
    for (std::thread &Thread : Threads)
    {
        Thread.join();
    }
}
Ruud
  • 97
  • 6

2 Answers2

0

GetStdTags is a non-static member function. You have access to this - the pointer to the object you're calling it on - inside it. Therefore when you call it, you need to specify the object which the member function should be working on.

Pool.execute(GetStdTags, wsFile, wsCol)

Here you only specify the two formal arguments, but not the object for the member function. Passing that along would look like this:

TrackTags one_object; // < yes, ONE, shared between the threads!
                      //   if you don't want to share it between threads,
                      //   you need to create multiple objects (and keep them alive)
Pool.execute(&TrackTags::GetStdTags, &one_object, wsFile, wsCol)
//           ^ pointer to member     ^ pointer
//             function                to object,
//                                     "this" inside the
//                                     member function

More information about std::bind and member functions.

However, since your class (TrackTags) doesn't have any data members and the member function GetStdTags doesn't need access to any object of type TrackTags, you can make it a static member function (or even a free function, really) and get by without a TrackTags object.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
0

Thank you Daniel.

The test project seemed to work, so I started on the "real" thing.

It is building without errors, but not working as planned. I tested with 4 threads and 4 files and ran it twice.

Result below: What did I miss?

B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\01 Adam Baldych - Bridges.flac
B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\02 Adam Baldych - Polesie.flac
B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\03 Adam Baldych - Mosaic.flac
B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\04 Adam Baldych - Riese.flac
6--Riese--Col Test
7--Riese--Col Test
8--Riese--Col Test
9--Riese--Col Test
B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\01 Adam Baldych - Bridges.flac
B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\02 Adam Baldych - Polesie.flac
B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\03 Adam Baldych - Mosaic.flac
B:\Music\FLAC_Unlisted\Adam Baldych\Bridges\04 Adam Baldych - Riese.flac

First run, completes, but with wrong results, and the second run immediately after the first completed hangs.

This is the function called:

TrackTags::TagsStruct TrackTags::GetTags(wxString wsFile, wxString wsCollection)
{
    // create Tag variable structure
    struct TagsStruct TagLine;
    .
    .
    return TagLine
}

It loads a DLL and extracts the required tags from a file to return it in a structure with the Thread-ID as first item (temporary to check how the code runs).

The calling function is below:

void TrackTags::GetMultiStdTags()
{
    wxString wsCol{"Col Test"};
    std::vector<wxString> wsTracks
    {
        "B:\\Music\\FLAC_Unlisted\\Adam Baldych\\Bridges\\01 Adam Baldych - Bridges.flac",
        "B:\\Music\\FLAC_Unlisted\\Adam Baldych\\Bridges\\02 Adam Baldych - Polesie.flac",
        "B:\\Music\\FLAC_Unlisted\\Adam Baldych\\Bridges\\03 Adam Baldych - Mosaic.flac",
        "B:\\Music\\FLAC_Unlisted\\Adam Baldych\\Bridges\\04 Adam Baldych - Riese.flac"
    };

    TrackTags one_object;
    Thread_Pool Pool(4);
    std::vector<std::future<TagsStruct>> StdFutures;

    for (auto &tr : wsTracks)
    {
        std::cout << tr << std::endl;
        StdFutures.push_back(Pool.execute(&TrackTags::GetTags, &one_object, tr, wsCol));
    }


    for (auto &Fut : StdFutures)
    {
        TagsStruct TSS;
        TSS = Fut.get();
        if (TSS.OK)
        { std::cout << TSS.ThreadID << "--" << TSS.TrackTitle << "--" << TSS.Collection << std::endl; }
        else
        { std::cout << "Empty Tag structure found\n"; }
    }
}
Ruud
  • 97
  • 6