i have a vector of objects std::vector and the fo object has a method start() where i create the thread specific to this object and now depends on a variable from this object i want to put it in sleep.
so for example if my object is f1 and the variable is bool sleep = false; when the sleep variable is true i want it to go to sleep.
i have tried this method but it doesn't seem to work. i think the if
class fo {
public :
thread* t ;
bool bedient = false , spazieren = false;
void start(){
t = new thread([this]() {
while (!this->bedient){
if (this->spazieren == true){
std::this_thread::sleep_for(std::chrono::seconds(10));
this->spazieren = false ;
}
}
this->join();
});
}
void join(){
t->join(); delete t;
}
};
You have "generated" a lot of problems on your code:
1)
Setting any kind of variable in one thread is potentially invisible in any other thread. If you want to make the other threads sees you changes in the first thread, you have to synchronize your memory. That can be done by using std::mutex with lock and unlock around every change of data or using std::atomic variables, which do the sync themselves or a lot of other methods. Please read a book about multi threaded programming!
2)
You try to join your own thread. That is not the correct usage at all. Any thread can join on others execution end but not on itself. That makes no sense!
3)
If you do not set manually the "sleep" var, your thread is running a loop and is simply doing nothing. A good method to heat up your core and the planet ;)
class fo {
public :
std::thread* t=nullptr ; // prevent corrupt delete if no start() called!
std::atomic<bool> bedient = false ;
std::atomic<bool> spazieren = false;
void start()
{
t = new std::thread([this]()
{
while (!this->bedient)
{
if (this->spazieren == true)
{
std::cout << "We are going to sleep" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
this->spazieren = false ;
}
}
std::cout << "End loop" << std::endl;
});
}
~fo() { delete t; }
void join()
{
std::cout << "wait for thread ends" << std::endl;
t->join();
}
};
int main()
{
fo f1;
f1.start();
sleep(1);
f1.spazieren = true;
sleep(1);
f1.bedient = true;
f1.join();
}
BTW:
Please do not use using namespace std!
Your design seems to be problematic. Setting vars from external threads to control execution of a thread is typically an abuse. You should think again for your design!
Manually using new/delete can be result in memory leaks.
Creating something with a start() method which later on will be deleted is mysterious. You should create all objects in the constructor.
I would try refactoring your code to use std::future instead of std::thread, furthermore there are a few issues which I believe you'll run into in the short term.
You shouldn't try to join while in the thread you're joining. That is, the code as you have it will never terminate. The lambda you've defined will attempt to call join, however, the lambda will never return since it's waiting on join which will only itself return when the lambda does so. In other words, you're telling it to wait on itself.
You're revealing too much information about the functionality of your class to the outside world. I would suggest moving implementation details into a .cc rather than putting it in the class declaration. Short of that, however, you're providing immediate access to your control variables spazieren and bedient. This is a problem because it complicates control flow and makes for weak abstraction.
Your bools are not atomic. If you attempt to modify them from outside the thread they're being read you'll run into crashes. And in some environments these crashes might be sporadic and very hard to debug.
Only sleeping when asked can be useful if you absolutely need to finish a task as soon as possible, but be aware that it's going to max out a core and if deployed to the wrong environment can cause major problems and slowdowns. I don't know what the end goal is for this program, but I would suggest considering changing the yield in the following code example to -some- period of time to sleep, 10 ms should be sufficient to prevent putting too much stress on your cpu.
Your threads status as to whether or not it's actively running is unclear with your implementation. I'd suggest considering an additional bool to indicate if it's running or not so you can more properly decide what to do if start() is called more than once.
When this object destructs it's going to crash if the thread is still running. You need to be sure to join before your destructor finishes running too.
I would consider the following refactorings:
#include <memory>
#include <future>
#include <atomic>
class fo
{
public:
~fo()
{
this->_bedient = true;
_workThread.wait();
}
void start()
{
_workThread = std::async(std::launch::async, [this]() -> bool
{
while(!this->_bedient)
{
if(true == this->_spazieren)
{
std::this_thread::sleep_for(std::chrono::seconds(10));
this->_spazieren = false;
}
else
{
std::this_thread::yield();
}
}
return true;
});
}
void ShouldSleep(bool shouldSleep)
{
this->_spazieren = shouldSleep;
}
void ShouldStop(bool shouldStop)
{
this->_bedient = !shouldStop;
}
private:
std::future<bool> _workThread = {};
std::atomic<bool> _bedient{ false };
std::atomic<bool> _spazieren{ false };
};
Related
I am trying to design an infinite (or a user-defined length) loop that would be independent of my GUI process. I know how to start that loop in a separate thread, so the GUI process is not blocked. However, I would like to have a possibility to interrupt the loop at a press of a button. The complete scenario may look like this:
GUI::startButton->myClass::runLoop... ---> starts a loop in a new thread
GUI::stopButton->myClass::terminateLoop ---> should be able to interrupt the started loop
The problem I have is figuring out how to provide the stop functionality. I am sure there is a way to achieve this in C++. I was looking at a number of multithreading related posts and articles, as well as some lectures on how to use async and futures. Most of the examples did not fit my intended use and/or were too complex for my current state of skills.
Example:
GUIClass.cpp
MyClass *myClass = new MyClass;
void MyWidget::on_pushButton_start_clicked()
{
myClass->start().detach();
}
void MyWidget::on_pushButton_stop_clicked()
{
myClass->stop(); // TBD: how to implement the stop functionality?
}
MyClass.cpp
std::thread MyClass::start()
{
return std::thread(&MyClass::runLoop, this);
}
void MyClass::runLoop()
{
for(int i = 0; i < 999999; i++)
{
// do some work
}
}
As far as i know, there is no standard way to terminate a STL thread. And even if possible, this is not advisable since it can leave your application in an undefined state.
It would be better to add a check to your MyClass::runLoop method that stops execution in a controlled way as soon as an external condition is fulfilled. This might, for example, be a control variable like this:
std::thread MyClass::start()
{
_threadRunning = true;
if(_thread.joinable() == true) // If thr thread is joinable...
{
// Join before (re)starting the thread
_thread.join();
}
_thread = std::thread(&MyClass::runLoop, this);
return _thread;
}
void MyClass::runLoop()
{
for(int i = 0; i < MAX_ITERATION_COUNT; i++)
{
if(_threadRunning == false) { break; }
// do some work
}
}
Then you can end the thread with:
void MyClass::stopLoop()
{
_threadRunning = false;
}
_threadRunning would here be a member variable of type bool or, if your architecture for some reason has non-atomic bools, std::atomic<bool>.
With x86, x86_64, ARM and ARM64, however, you should be fine without atomic bools. It, however is advised to use them. Also to hint at the fact that the variable is used in a multithreading context.
Possible MyClass.h:
MyClass
{
public:
MyClass() : _threadRunning(false) {}
std::thread start();
std::thread runLoop();
std::thread stopLoop();
private:
std::thread _thread;
std::atomic<bool> _threadRunning;
}
It might be important to note that, depending on the code in your loop, it might take a while before the thread really stops.
Therefore it might be wise to std::thread::join the thread before restarting it, to make sure only one thread runs at a time.
In our program, we have a class FooLogger which logs specific events (strings). We use the FooLogger as a unique_ptr.
We have two threads which use this unique_ptr instance:
Thread 1 logs the latest event to file in a while loop, first checking if the instance is not nullptr
Thread 2 deallocates the FooLogger unique_ptr instance when the program has reached a certain point (set to nullptr)
However, due to bad interleaving, it is possible that, while logging, the member variables of FooLogger are deallocated, resulting in an EXC_BAD_ACCESS error.
class FooLogger {
public:
FooLogger() {};
void Log(const std::string& event="") {
const float32_t time_step_s = timer_.Elapsed() - runtime_s_; // Can get EXC_BAD_ACCESS on timer_
runtime_s_ += time_step_s;
std::cout << time_step_s << runtime_s_ << event << std::endl;
}
private:
Timer timer_; // Timer is a custom class
float32_t runtime_s_ = 0.0;
};
int main() {
auto foo_logger = std::make_unique<FooLogger>();
std::thread foo_logger_thread([&] {
while(true) {
if (foo_logger)
foo_logger->Log("some event");
else
break;
}
});
SleepMs(50); // pseudo code
foo_logger = nullptr;
foo_logger_thread.join();
}
Is it possible, using some sort of thread synchronisation/locks etc. to ensure that the foo_logger instance is not deallocated while logging? If not, are there any good ways of handling this case?
The purpose of std::unique_ptr is to deallocate the instance once std::unique_ptr is out of scope. In your case, you have multiple threads each having access to the element and the owning thread might get eliminated prior to other users.
You either need to ensure that owner thread never gets deleted prior to the user threads or change ownership model from std::unique_ptr to std::shared_ptr. It is the whole purpose of std::shared_ptr to ensure that the object is alive as long as you use it.
You just need to figure out what's required for program and use the right tools to achieve it.
Use a different mechanism than the disappearance of an object for determining when to stop.
(When you use a single thing for two separate purposes, you often get into trouble.)
For instance, an atomic bool:
int main() {
FooLogger foo_logger;
std::atomic<bool> keep_going = true;
std::thread foo_logger_thread([&] {
while(keep_going) {
foo_logger.Log("some event");
}
});
SleepMs(50);
keep_going = false;
foo_logger_thread.join();
}
It sounds like std::weak_ptr can help in this case.
You can make one from a std::shared_ptr and pass it to the logger thread.
For example:
class FooLogger {
public:
void Log(std::string const& event) {
// log the event ...
}
};
int main() {
auto shared_logger = std::make_shared<FooLogger>();
std::thread foo_logger_thread([w_logger = std::weak_ptr(shared_logger)]{
while (true) {
auto logger = w_logger.lock();
if (logger)
logger->Log("some event");
else
break;
}
});
// some work ...
shared_logger.reset();
foo_logger_thread.join();
}
Use should use make_shared instead of make_unique. And change:
std::thread foo_logger_thread([&] {
to
std::thread foo_logger_thread([foo_logger] {
It will create new instance of shared_ptr.
I have been working on a idea for a system where I can have many workers that are triggered on a regular basis by a a central timer class. The part I'm concerned about here is a TriggeredWorker which, in a loop, uses the mutex & conditionVariable approach to wait to be told to do work. It has a method trigger that is called (by a different thread) that triggers work to be done. It is an abstract class that has to be subclassed for the actual work method to be implemented.
I have a test that shows that this mechanism works. However, as I increase the load by reducing the trigger interval, the test starts to fail. When I delay 20 microseconds between triggers, the test is 100% reliable. As I reduce down to 1 microsecond, I start to get failures in that the count of work performed reduces from 1000 (expected) to values like 986, 933, 999 etc..
My questions are: (1) what is it that is going wrong and how can I capture what is going wrong so I can report it or do something about it? And, (2) is there some better approach that I could use that would be better? I have to admit that my experience with c++ is limited to the last 3 months, although I have worked with other languages for several years.
Many thanks for reading...
Here are the key bits of code:
Triggered worker header file:
#ifndef TIMER_TRIGGERED_WORKER_H
#define TIMER_TRIGGERED_WORKER_H
#include <thread>
#include <plog/Log.h>
class TriggeredWorker {
private:
std::mutex mutex_;
std::condition_variable condVar_;
std::atomic<bool> running_{false};
std::atomic<bool> ready_{false};
void workLoop();
protected:
virtual void work() {};
public:
void start();
void stop();
void trigger();
};
#endif //TIMER_TRIGGERED_WORKER_H
Triggered worker implementation:
#include "TriggeredWorker.h"
void TriggeredWorker::workLoop() {
PLOGD << "workLoop started...";
while(true) {
std::unique_lock<std::mutex> lock(mutex_);
condVar_.wait(lock, [this]{
bool ready = this->ready_;
bool running = this->running_;
return ready | !running; });
this->ready_ = false;
if (!this->running_) {
break;
}
PLOGD << "Calling work()...";
work();
lock.unlock();
condVar_.notify_one();
}
PLOGD << "Worker thread completed.";
}
void TriggeredWorker::start() {
PLOGD << "Worker start...";
this->running_ = true;
auto thread = std::thread(&TriggeredWorker::workLoop, this);
thread.detach();
}
void TriggeredWorker::stop() {
PLOGD << "Worker stop.";
this->running_ = false;
}
void TriggeredWorker::trigger() {
PLOGD << "Trigger.";
std::unique_lock<std::mutex> lock(mutex_);
ready_ = true;
lock.unlock();
condVar_.notify_one();
}
and the test:
#include "catch.hpp"
#include "TriggeredWorker.h"
#include <thread>
TEST_CASE("Simple worker performs work when triggered") {
static std::atomic<int> twt_count{0};
class SimpleTriggeredWorker : public TriggeredWorker {
protected:
void work() override {
PLOGD << "Incrementing counter.";
twt_count.fetch_add(1);
}
};
SimpleTriggeredWorker worker;
worker.start();
for (int i = 0; i < 1000; i++) {
worker.trigger();
std::this_thread::sleep_for(std::chrono::microseconds(20));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
CHECK(twt_count == 1000);
std::this_thread::sleep_for(std::chrono::seconds(1));
worker.stop();
}
What happens when worker.trigger() is called twice before workLoop acquires the lock? You loose one of those "triggers". Smaller time gap means higher probability of test failure, because of higher probability of multiple consecutive worker.trigger() calls before workLoop wakes up. Note that there's nothing that guarantees that workLoop will acquire the lock after worker.trigger() but before another worker.trigger() happens, even when those calls happen one after another (i.e. not in parallel). This is governed by the OS scheduler and we have no control over it.
Anyway the core problem is that setting ready_ = true twice looses information. Unlike incrementing an integer twice. And so the simplest solution is to replace bool with int and do inc/dec with == 0 checks. This solution is also known as semaphore. More advanced (potentially better, especially when you need to pass some data to the worker) approach is to use a (bounded?) thread safe queue. That depends on what exactly you are trying to achieve.
BTW 1: all your reads and updates, except for stop() function (and start() but this isn't really relevant), happen under the lock. I suggest you fix stop() to be under lock as well (since it is rarely called anyway) and turn atomics into non-atomics. There's an unnecessary overhead of atomics at the moment.
BTW 2: I suggest not using thread.detach(). You should store the std::thread object on TriggeredWorker and add destructor that does stop with join. These are not independent beings and so without detach() you make your code safer (one should never die without the other).
I have this race condition with an audio playback class, where every time I start playback I set keepPlaying as true, and false when I stop.
The problem happens when I stop() immediately after I start, and the keepPlaying flag is set to false, then reset to true again.
I could put a delay in stop(), but I don't think that's a very good solution. Should I use conditional variable to make stop() wait until keepPlaying is true?
How would you normally solve this problem?
#include <iostream>
#include <thread>
using namespace std;
class AudioPlayer
{
bool keepRunning;
thread thread_play;
public:
AudioPlayer(){ keepRunning = false; }
~AudioPlayer(){ stop(); }
void play()
{
stop();
// keepRunning = true; // A: this works OK
thread_play = thread(&AudioPlayer::_play, this);
}
void stop()
{
keepRunning = false;
if (thread_play.joinable()) thread_play.join();
}
void _play()
{
cout << "Playing: started\n";
keepRunning = true; // B: this causes problem
while(keepRunning)
{
this_thread::sleep_for(chrono::milliseconds(100));
}
cout << "Playing: stopped\n";
}
};
int main()
{
AudioPlayer ap;
ap.play();
ap.play();
ap.play();
return 0;
}
Output:
$ ./test
Playing: started
(pause indefinitely...)
Here is my suggestion, combining many comments from below as well:
1) Briefly synchronized the keepRunning flag with a mutex so that it cannot be modified while a previous thread is still changing state.
2) Changed the flag to atomic_bool, as it is also modified while the mutex is not used.
class AudioPlayer
{
thread thread_play;
public:
AudioPlayer(){ }
~AudioPlayer()
{
keepRunning = false;
thread_play.join();
}
void play()
{
unique_lock<mutex> l(_mutex);
keepRunning = false;
if ( thread_play.joinable() )
thread_play.join();
keepRunning = true;
thread_play = thread(&AudioPlayer::_play, this);
}
void stop()
{
unique_lock<mutex> l(_mutex);
keepRunning = false;
}
private:
void _play()
{
cout << "Playing: started\n";
while ( keepRunning == true )
{
this_thread::sleep_for(chrono::milliseconds(10));
}
cout << "Playing: stopped\n";
}
atomic_bool keepRunning { false };
std::mutex _mutex;
};
int main()
{
AudioPlayer ap;
ap.play();
ap.play();
ap.play();
this_thread::sleep_for(chrono::milliseconds(100));
ap.stop();
return 0;
}
To answer the question directly.
Setting keepPlaying=true at point A is synchronous in the main thread but setting it at point B it is asynchronous to the main thread.
Being asynchronous the call to ap.stop() in the main thread (and the one in the destructor) might take place before point B is reached (by the asynchronous thread) so the last thread runs forever.
You should also make keepRunning atomic that will make sure that the value is communicated between the threads correctly. There's no guarantee of when or if the sub-thread will 'see' the value set by the main thread without some synchronization. You could also use a std::mutex.
Other answers don't like .join() in stop(). I would say that's a design decision. You certainly need to make sure the thread has stopped before leaving main()(*) but that could take place in the destructor (as other answers suggest).
As a final note the more conventional design wouldn't keep re-creating the 'play' thread but would wake/sleep a single thread. There's an overhead of creating a thread and the 'classic' model treats this as a producer/consumer pattern.
#include <iostream>
#include <thread>
#include <atomic>
class AudioPlayer
{
std::atomic<bool> keepRunning;
std::thread thread_play;
public:
AudioPlayer():keepRunning(false){
}
~AudioPlayer(){ stop(); }
void play()
{
stop();
keepRunning = true; // A: this works OK
thread_play = std::thread(&AudioPlayer::_play, this);
}
void stop()
{
keepRunning=false;
if (thread_play.joinable()){
thread_play.join();
}
}
void _play()
{
std::cout<<"Playing: started\n";
while(keepRunning)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout<<"Playing: stopped\n";
}
};
int main()
{
AudioPlayer ap;
ap.play();
ap.play();
ap.play();
ap.stop();
return 0;
}
(*) You can also detach() but that's not recommended.
First, what you have here is indeed the definition of a data race - one thread is writing to a non-atomic variable keepRunning and another is reading from it. So even if you uncomment the line in play, you'd still have a data race. To avoid that, make keepRunning a std::atomic<bool>.
Now, the fundamental problem is the lack of symmetry between play and stop - play does the actual work in a spawned thread, while stop does it in the main thread. To make the flow easier to reason about, increase symmetry:
set keepRunning in play, or
have play wait for the thread to be up and running and done with any setup (also eliminating the need for the if in stop).
As a side note, one way to handle cases where a flag is set and reset in possibly uneven order is to replace it with a counter. You then stall until you see the expected value, and only then apply the change (using CAS).
Ideally, you'd just set keepPlaying before starting the thread (as in your commented out play() function). That's the neatest solution, and skips the race completely.
If you want to be more fancy, you can also use a condition_variable and signal the playing thread with notify_one or notify_all, and in the loop check wait_until with a duration of 0. If it's not cv_status::timeout then you should stop playing.
Don't make stop pause and wait for state to settle down. That would work here, but is a bad habit to get into for later.
As noted in the comment, it is undefined behavior to write to a variable while simultaneously reading from it. atomic<bool> solves this, but wouldn't fix your race on its own, it just makes the reads and writes well defined.
I modified your program a bit and it works now. Let's discuss problems first:
Problem 1: using plain bool variable in 2 threads
Here both threads update the variable and it might lead to a race condition, because it is highly dependent which thread comes first and even end up in undefined behaviour. Undefined behaviour especially might occur when write from one thread is interrupted by another. Here Snps brought up links to the following SO answers:
When do I really need to use atomic<bool> instead of bool?
trap representation
In addition I was searching if write can be interrupted for bool on x86 platforms and came across this answer:
Can a bool read/write operation be not atomic on x86?
Problem 2: Caching as compiler optimization
Another problem is that variables are allowed to be cached. It means that the «playing thread» might cache the value of keepRunning and thus never terminate or terminate after considerable amount of time. In previous C++ version (98, 2003) a volatile modifier was the only construct to mark variables to prevent/avoid caching optimization and in this case force the compiler to always read the variable from its actual memory location. Thus given the «playing thread» enters the while loop keepRunning might be cached and never read or with considerable delays no matter when stop() modifies it.
After C++ 11 atomic template and atomic_bool specialization were introduced to make such variables as non-cachable and being read/set in an uninterruptible manner, thus adressing Problems 1 & 2.
Side note: volatile and caching explained by Andrei Alexandrescu in the Dr. Dobbs article which addresses exactly this situation:
Caching variables in registers is a very valuable optimization that applies most of the time, so it would be a pity to waste it. C and C++ give you the chance to explicitly disable such caching. If you use the volatile modifier on a variable, the compiler won't cache that variable in registers — each access will hit the actual memory location of that variable.
Problem 3: stop was called before _play() function was even started
The problem here is that in multi-threaded OSs scheduler grants some time slice for a thread to run. If the thread can progress and this time slice is not over thread continues to run. In «main thread» all play() calls were executed even before the «play threads» started to run. Thus the object destruction took place before _play() function started running. And there you set the variable keepRunning to true.
How I fixed this problem
We need to ensure that play() returns when the _play() function started running. A condition_variable is of help here. play() blocks so long until _play() notifies it that it has started the execution.
Here is the code:
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
class AudioPlayer
{
atomic_bool keepRunning;
thread thread_play;
std::mutex mutex;
std::condition_variable play_started;
public:
AudioPlayer()
: keepRunning{false}
{}
~AudioPlayer(){ stop(); }
void play()
{
stop();
std::unique_lock<std::mutex> lock(mutex);
thread_play = thread(&AudioPlayer::_play, this);
play_started.wait(lock);
}
void stop()
{
keepRunning = false;
cout << "stop called" << endl;
if (thread_play.joinable()) thread_play.join();
}
void _play()
{
cout << "Playing: started\n";
keepRunning = true; // B: this causes problem
play_started.notify_one();
while(keepRunning)
{
this_thread::sleep_for(chrono::milliseconds(100));
}
cout << "Playing: stopped\n";
}
};
int main()
{
AudioPlayer ap;
ap.play();
ap.play();
ap.play();
return 0;
}
Your solution A is actually almost correct. It's still undefined behavior to have one thread read from non-atomic variable that another is writing to. So keepRunning must be made an atomic<bool>. Once you do that and in conjunction with your fix from A, your code will be fine. That is because stop now has a correct post condition that no thread will be active (in particular no _play call) after it exits.
Note that no mutex is necessary. However, play and stop are not themselves thread safe. As long as the client of AudioPlayer is not using the same instance of AudioPlayer in multiple threads though that shouldn't matter.
Here is a simplified version of what I am trying to do:
#include <iostream>
#include <vector>
#include <thread>
#include <atomic>
class client {
private:
std::vector<std::thread> threads;
std::atomic<bool> running;
void main() {
while(running) {
std::cout << "main" << std::endl;
}
}
void render() {
while(running) {
std::cout << "render" << std::endl;
}
}
public:
client() {
running = true;
threads.push_back(std::thread(&client::main, this));
threads.push_back(std::thread(&client::render, this));
}
~client() {
running = false;
for(auto& th : threads) th.join();
};
};
int main() {
client c;
std::string inputString;
getline(std::cin, inputString);
return 0;
}
(Note: code has been changed since question was written)
What I am trying to do is create a class that holds threads for the main loop(of the class), rendering, and a couple other things. However I cannot get this simplified version to work. I have tried using mutex to lock and unlock the threads, but didn't seem to help any. I do not know why it is not working, but I suspect that it is a result of the use of this in threads.push_back(std::thread(this->main, this));.
The current structure of the code doesn't have to remain... The only requirement is that uses one of it's own member functions as a thread (and that, that thread is stored in the class). I am not sure if this requires two classes or if my attempt to do it in one class was the correct approach. I have seen many examples of creating an object, and then calling a member that creates threads. I am trying to avoid this and instead create the threads within the constructor.
The problem here is that you do not wait for the threads to end. In main you create c. This then spawns the threads. The next thing to happen is to return which destroys c. When c is destroyed it destroys its members. Now when a thread is destroyed if it has not been joined or detached then std::terminate is called and the program ends
What you need to do is in the destructor, set running to false and then call join on both the threads. This will stop the loop in each thread and allow c to be destructed correctly.
Doing this however brings up another issue. running is not an atomic variable so writing to it while threads are reading it is undefined behavior. We can fin that though by changing running to a std::atomic<bool> which provides synchronization.
I also had to make a change to the thread construction. When you want to use a member function the syntax should be
std::thread(&class_name::function_name, pointer_to_instance_of_class_name, function_parameters)
so in this case it would be
threads.push_back(std::thread(&client::main, this));
threads.push_back(std::thread(&client::render, this));