3

I'm curious to what I'm doing wrong here. I have the following function:

indexer.h:

class DtIndexer {
public:
    static void ThreadedIndex(string folderPath);

indexer.cpp

void DtIndexer::ThreadedIndex(string folderPath) {
    cout << "\t-> Indexing Folder: " << folderPath << endl;
    cout << "\t->> Done..." << endl;
}

and my call to create the threads:

void DtIndexer::UpdateIndex(DatabaseData &data, bool isCreate) {
    vector<thread> threadList;

    for (string &s: data.FilePaths) {
        const char *folder = GetFolderPath(s, data.IncludeSubFolders);
        cout << "\t-> Adding Folder to Thread: " << folder << endl;
        threadList.emplace_back(thread(ThreadedIndex, folder));
    }

    for_each(threadList.begin(), threadList.end(), mem_fn(&thread::join));
}

My output is this:

-> Adding Folder to Thread: /index_2185<+>

-> Adding Folder to Thread: /index_1065<+>

-> Indexing Folder: /index_1065<+>

->> Done...

-> Indexing Folder: /index_1065<+>

->> Done...

Now, I'm pretty sure it has to deal with the static for the method, but if I remove static I get this:

error: invalid use of non-static member function ‘void DtIndexer::ThreadedIndex(std::__cxx11::string)’ threadList.emplace_back(thread(ThreadedIndex, folder));

Also, If I remove static and add the function to the thread like this:

threadList.emplace_back(thread(&DtIndexer::ThreadedIndex, folder));

I get:

required from here /usr/include/c++/6/functional:1286:7: error: static assertion failed: Wrong number of arguments for pointer-to-member static_assert(_Varargs::value

I'm still fairly new to C++, so, any advice will be appreciated.

CodeLikeBeaker
  • 18,503
  • 12
  • 73
  • 102
  • What does `GetFolderPath` do? – tkausl Apr 17 '19 at 18:00
  • @tkausl it just builds the path and returns it. That is a custom function that I have, and it works just fine. Basically it appends the on the end of the path for specific indexes – CodeLikeBeaker Apr 17 '19 at 18:00
  • 2
    When non-`static`, `DtIndexer::ThreadedIndex` must be invoked on an `DtIndexer` instance to get a valid `this`. That instance is your missing parameter. If you don't need `this`, it's best to stay `static` or use a [free function](https://stackoverflow.com/questions/4861914/what-is-the-meaning-of-the-term-free-function-in-c). – user4581301 Apr 17 '19 at 18:02
  • @user4581301 Ahh... I see what you mean. Let me see what I can do with that – CodeLikeBeaker Apr 17 '19 at 18:07
  • Warning: the `DtIndexer` instances must survive at least until the thread exits, so you'll have to keep them somewhere. If you are thinking of storing `DtIndexer` instances in a `vector`, you'll have to worry about [Iterator Invalidation](https://stackoverflow.com/questions/6438086/iterator-invalidation-rules). With a bit more context on how you plan on using this code (and probably a bit more code) we can offer up better suggestions on how to get what you want. – user4581301 Apr 17 '19 at 18:11
  • @user4581301 good to know. makes sense. looks like I need to re-think how I'm structuring my code – CodeLikeBeaker Apr 17 '19 at 18:12
  • 3
    Are you aware that in this line `thread(ThreadedIndex, folder)` is called `thread(&ThreadedIndex, const char*)` which second param refers to local variable, and when iteration of for loop ends, string is destroyed. So when body of threads are executed string `folderPath` is created from dangling pointer. You should pass string by value instead of `const char*` to thread. – rafix07 Apr 17 '19 at 18:15
  • 1
    @rafix07 Yes, thank you. That was the problem. I'm also going to look into the information user4581301 added as well. Thanks again all. If you want to post this as the answer (for my specific problem), I'll mark it as accepted – CodeLikeBeaker Apr 17 '19 at 18:26
  • 1
    Your question lacks a [mcve]. – Ulrich Eckhardt Apr 17 '19 at 18:41
  • @UlrichEckhardt I'd disagree, but that's fine :) – CodeLikeBeaker Apr 17 '19 at 18:43

1 Answers1

3

Replace

const char *folder = GetFolderPath(s, data.IncludeSubFolders);

with

std::string folder( GetFolderPath(s, data.IncludeSubFolders) );

The thread starts by copying the arguments by value and you copy a const char* which probably points to a static buffer in the GetFolderPath function. That buffer will be overwritten for each call you do so the threads that is reading from it may or may not get what you expect.

Note that you don't need to do emplace_back(std::thread(... since what you write as arguments to emplace_back is forwarded to the constructor of the type your vector is holding, which in this case is std::thread. You also do not need to make the thread function static unless you really want to. You could start with a lambda, capturing this and folder by copy. Another thing you may notice is that the output gets garbled when many threads write to std::cout at the same time. You can guard common resources (like std::cout) against simultaneous accesses by multiple threads using std::mutex and std::lock_guard.

Here's a version of your class with a non-static thread method and a guard for std::cout:

#include <iostream>
#include <vector>
#include <cstring>
#include <mutex>
#include <thread>

const char* GetFolderPath() {
    static std::vector<std::string> paths = {"this is a path", "here is another one",
                                             "what do we have here", "mother of dirs",
                                             "what did the tomatoe ..."};
    static size_t idx = 0;
    static char buf[256];
    std::strcpy(buf, paths[idx].c_str());
    idx = (idx + 1) % paths.size();
    return buf;
}

class DtIndexer {
    std::mutex mtx_cout; // std::cout mutex

public:
    void ThreadedIndex(std::string folderPath) {
        std::lock_guard lock(mtx_cout); // wait until this thread acquires the mutex
        std::cout << "\t-> Indexing Folder: " << folderPath << "\n";
    }

    void UpdateIndex() {
        std::vector<std::thread> threadList;

        { // lock_guard scope
            std::lock_guard lock(mtx_cout); // acquire mutex

            for(int i = 0; i < 50; ++i) {
                // const char* folder = GetFolderPath();
                std::string folder(GetFolderPath());

                std::cout << "\t-> Adding Folder to Thread: " << folder << "\n";

                //             safe copy here --+       +--- thread runs here ---+
                //                              |       |                        |
                //                              V       V                        V
                threadList.emplace_back( [this, folder] { ThreadedIndex(folder); });
                //                      Δ
                //                      |
                //                      +-- no std::thread(... needed here
            }
        } // lock_guard releases mutex here

        for(auto& t : threadList) t.join();
    }
};

int main() {
    DtIndexer a;
    a.UpdateIndex();
}

You can replace the folder string above with a const char* and see the same problem you got.

Ted Lyngmo
  • 37,764
  • 5
  • 23
  • 50
  • Thank you, this makes perfect sense and coincides with what the other comments were suggesting. I appreciate the detailed explanation. – CodeLikeBeaker Apr 17 '19 at 18:33