Nick Nick - 1 month ago 30
C++ Question

C++11: Segfault with std::thread and lambda function

I've written a small application to demonstrate the issue, it is not pretty, but it does the job.

#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>

class A {
public:
A() : thread_(), tasks_(), mutex_(), a_(99999) {}

void Start() {
thread_ = std::thread([this] () { Run(); });
}

private:
using Task = std::function<void()>;

public:
void AddTask(const Task& task) {
std::lock_guard<std::mutex> lock(mutex_);

tasks_.push(task);
}

bool Empty() {
std::lock_guard<std::mutex> lock(mutex_);

const bool empty = tasks_.empty();

return empty;
}

Task GetTask() {
std::lock_guard<std::mutex> lock(mutex_);

const auto& task = tasks_.front();

tasks_.pop();

return task;
}

int GetInt() { return a_; }

void Join() { thread_.join(); }

private:
void Run() {
while (Empty());

(GetTask())();
}

std::thread thread_;
std::queue<Task> tasks_;
std::mutex mutex_;
int a_;
};

template <class Base>
class B : public Base {
public:
using Base::Base;

void Start() {
Base::Start();
std::cout << "A: " << this << std::endl;
Base::AddTask([this] () { std::cout << "T: " << this << std::endl; Do(); });
}

void Do() {
std::cout << "Do: " << this << std::endl;
std::cout << "GetInt: " << Base::GetInt() << std::endl;
}
};

int main() {
B<A> app;
app.Start();
app.Join();
}


clang++ -std=c++11 -lpthread test_a.cpp

A: 0x7ffeb521f4e8
T: 0x21ee540
Do: 0x21ee540
GetInt: 0


Notice the change in 'this' and the value of 0 for 'GetInt'.

I'm really lost here... Any help would be greatly appreciated,

Thanks.

Answer

I reduced your reproduction to:

#include <functional>
#include <iostream>
#include <queue>

struct foo {
  using Task = std::function<void()>;

  void Test() {
    std::cout << "In Test, this: " << this << std::endl;
    AddTask([this] { std::cout << "In task, this: " << this << std::endl; });
  }

  void AddTask(const Task& task) {
    tasks_.push(task);
  }

  Task GetTask() {
    const auto& task = tasks_.front();
    tasks_.pop();
    return task;
  }

  std::queue<Task> tasks_;
};

int main() {
  foo f;
  f.Test();
  auto func = f.GetTask();
  func();
}

Do you see the problem now? The issue lies with:

const auto& task = tasks_.front();
tasks_.pop();

Here you grab a reference to an object, then you tell the queue to go ahead and delete that object. Your reference is now dangling, and chaos ensues.

You should move it out instead:

Task GetTask() {
  auto task = std::move(tasks_.front());
  tasks_.pop();
  return task;
}