Accessing random number engine from multiple threads - c++

this is my first question, so please forgive me any violations against your policy. I want to have one global random number engine per thread, to which purpose I've devised the following scheme: Each thread I start gets a unique index from an atomic global int. There is a static vector of random engines, whose i-th member is thought to be used by the thread with the index i. If the index if greater than the vector size elements are added to it in a synchronized manner. To prevent performance penalties, I check twice if the index is greater than the vector size: once in an unsynced manner, and once more after locking the mutex. So far so good, but the following example fails with all sorts of errors (heap corruption, malloc-errors, etc.).
#include<vector>
#include<thread>
#include<mutex>
#include<atomic>
#include<random>
#include<iostream>
using std::cout;
std::atomic_uint INDEX_GEN{};
std::vector<std::mt19937> RNDS{};
float f = 0.0f;
std::mutex m{};
class TestAThread {
public:
TestAThread() :thread(nullptr){
cout << "Calling constructor TestAThread\n";
thread = new std::thread(&TestAThread::run, this);
}
TestAThread(TestAThread&& source) : thread(source.thread){
source.thread = nullptr;
cout << "Calling move constructor TestAThread. My ptr is " << thread << ". Source ptr is" << source.thread << "\n";
}
TestAThread(const TestAThread& source) = delete;
~TestAThread() {
cout << "Calling destructor TestAThread. Pointer is " << thread << "\n";
if (thread != nullptr){
cout << "Deleting thread pointer\n";
thread->join();
delete thread;
thread = nullptr;
}
}
void run(){
int index = INDEX_GEN.fetch_add(1);
std::uniform_real_distribution<float> uniformRnd{ 0.0f, 1.0f };
while (true){
if (index >= RNDS.size()){
m.lock();
// add randoms in a synchronized manner.
while (index >= RNDS.size()){
cout << "index is " << index << ", size is " << RNDS.size() << std::endl;
RNDS.emplace_back();
}
m.unlock();
}
f += uniformRnd(RNDS[index]);
}
}
std::thread* thread;
};
int main(int argc, char* argv[]){
std::vector<TestAThread> threads;
for (int i = 0; i < 10; ++i){
threads.emplace_back();
}
cout << f;
}
What am I doing wrong?!

Obviously f += ... would be a race-condition regardless of the right-hand side, but I suppose you already knew that.
The main problem that I see is your use of the global std::vector<std::mt19937> RNDS. Your mutex-protected critical section only encompasses adding new elements; not accessing existing elements:
... uniformRnd(RNDS[index]);
That's not thread-safe because resizing RNDS in another thread could cause RNDS[index] to be moved into a new memory location. In fact, this could happen after the reference RNDS[index] is computed but before uniformRnd gets around to using it, in which case what uniformRnd thinks is a Generator& will be a dangling pointer, possibly to a newly-created object. In any event, uniformRnd's operator() makes no guarantee about data races [Note 1], and neither does RNDS's operator[].
You could get around this problem by:
computing a reference (or pointer) to the generator within the protected section (which cannot be contingent on whether the container's size is sufficient), and
using a std::deque instead of a std::vector, which does not invalidate references when it is resized (unless the referenced object has been removed from the container by the resizing).
Something like this (focusing on the race condition; there are other things I'd probably do differently):
std::mt19937& get_generator(int index) {
std::lock_guard<std::mutex> l(m);
if (index <= RNDS.size()) RNDS.resize(index + 1);
return RNDS[index];
}
void run(){
int index = INDEX_GEN.fetch_add(1);
auto& gen = get_generator(index);
std::uniform_real_distribution<float> uniformRnd{ 0.0f, 1.0f };
while (true) {
/* Do something with uniformRnd(gen); */
}
}
[1] The prototype for operator() of uniformRnd is template< class Generator > result_type operator()( Generator& g );. In other words, the argument must be a mutable reference, which means that it is not implicitly thread-safe; only const& arguments to standard library functions are free of data races.

Related

Threading returns unexpected result - c++

I'm learning about threads for homework, and I've tried to implement threading on a simple program I've made. Without threading the program works perfectly, but when I thread the two random number generator functions, it returns incorrect results. The result always seems to be '42' for both number generators, not sure why this would be the case.
Also for context, I'm just starting with threads so I understand this program doesn't need multithreading. I'm doing it just for learning purposes.
Thanks for any help!
// struct for vector to use
struct readings {
std::string name;
int data;
};
// random generator for heat value - stores in vector of struct
void gen_heat(std::vector<readings>& storage) {
readings h = {"Heat", rand() % 100 + 1};
storage.insert(storage.begin(), h);
}
// random generator for light value - stores in vector of struct
void gen_light(std::vector<readings>& storage) {
readings l = {"Light", rand() % 100 + 1};
storage.insert(storage.begin(), l);
}
int main() {
// vector of readings struct
std::vector<readings> storage;
srand(time(NULL));
// initialising threads of random generators
std::thread H(gen_heat, std::ref(storage));
std::thread L(gen_light, std::ref(storage));
// waiting for both to finish
H.join();
L.join();
// print values in vec of struct
for (const auto& e : storage) {
std::cout << "Type: " << e.name << std::endl
<< "Numbers: " << e.data << std::endl;
}
// send to another function
smartsensor(storage);
return 0;
}
Since you have several threads accessing a mutual resource, in this case the vector of readings, and some of them are modifying it, you need to make the accesses to that resource exclusive. There are many ways of synchronizing the access; one of them, simple enough and not going down to the use of mutexes, is a binary semaphore (since C++20). You basically:
own the access to the resource by acquiring the semaphore,
use the resource, and then,
release the semaphore so others can access the resource.
If a thread A tries to acquire the semaphore while other thread B is using the resource, thread A will block until the resource is freed.
Notice the semaphore is initialized to 1 indicating the resource is free. Once a thread acquires the semaphore, the count will go down to 0, and no other thread will be able to acquire it until the count goes back to 1 (what will happen after a release).
[Demo]
#include <cstdlib> // rand
#include <iostream> // cout
#include <semaphore>
#include <string>
#include <thread>
#include <vector>
std::binary_semaphore readings_sem{1};
// struct for vector to use
struct readings {
std::string name;
int data;
};
// random generator for heat value - stores in vector of struct
void gen_heat(std::vector<readings>& storage) {
for (auto i{0}; i < 5; ++i) {
readings_sem.acquire();
readings h = {"Heat", rand() % 100 + 1};
storage.insert(storage.begin(), h);
readings_sem.release();
}
}
// random generator for light value - stores in vector of struct
void gen_light(std::vector<readings>& storage) {
for (auto i{0}; i < 5; ++i) {
readings_sem.acquire();
readings l = {"Light", rand() % 100 + 1};
storage.insert(storage.begin(), l);
readings_sem.release();
}
}
int main() {
// vector of readings struct
std::vector<readings> storage;
srand(time(NULL));
// initialising threads of random generators
std::thread H(gen_heat, std::ref(storage));
std::thread L(gen_light, std::ref(storage));
// waiting for both to finish
H.join();
L.join();
// print values in vec of struct
for (const auto& e : storage) {
std::cout << "Type: " << e.name << std::endl
<< "Numbers: " << e.data << std::endl;
}
}
// Outputs (something like):
//
// Type: Heat
// Numbers: 5
// Type: Light
// Numbers: 83
// Type: Light
// Numbers: 40
// ...
[Update on Ben Voigt's comment]
The acquisition and release of the resource can be encapsulated by using RAII (Resource Acquisition Is Initialization), a mechanism which is already provided by the language. E.g.:
Both threads still try and acquire a mutex to get access to the vector of readings resource.
But they acquire it by just creating a lock guard.
Once the lock guard goes out of scope and is destroyed, the mutex is released.
[Demo]
#include <mutex> // lock_guard
std::mutex mtx{};
// random generator for heat value - stores in vector of struct
void gen_heat(std::vector<readings>& storage) {
for (auto i{0}; i < 5; ++i) {
std::lock_guard<std::mutex> lg{ mtx };
readings h = {"Heat", rand() % 100 + 1};
storage.insert(storage.begin(), h);
}
}

Debugging the use of std::string in a thread pool C++

I'm in the process of trying to figure out multithreading - I'm pretty new to it. I'm using a thread_pool type that I found here. For sufficiently large N, the following code segfaults. Could you guys help me understand why and how to fix?
#include "thread_pool.hpp"
#include <thread>
#include <iostream>
static std::mutex mtx;
void printString(const std::string &s) {
std::lock_guard lock(mtx);
std::hash<std::thread::id> tid{};
auto id = tid(std::this_thread::get_id()) % 16;
std::cout << "thread: " << id << " " << s << std::endl;
}
TEST(test, t) {
thread_pool pool(16);
int N = 1000000;
std::vector<std::string> v(N);
for (int i = 0; i < N; i++) {
v[i] = std::to_string(i);
}
for (auto &s: v) {
pool.push_task([&s]() {
printString(s);
});
}
}
Here's the thread sanitizer output (note the ===> comments where I direct you to appropriate line"):
SEGV on unknown address 0x000117fbdee8 (pc 0x000102fa35b6 bp 0x7e8000186b50 sp 0x7e8000186b30 T257195)
0x102fa35b6 std::basic_string::__get_short_size const string:1514
0x102fa3321 std::basic_string::size const string:970
0x102f939e6 std::operator<<<…> ostream:1056
0x102f9380b printString RoadRunnerMapTests.cpp:37 // ==> this line: void printString(const std::string &s) {
0x102fabbd5 $_0::operator() const RoadRunnerMapTests.cpp:49 // ===> this line: v[i] = std::to_string(i);
0x102fabb3d (test_cxx_api_RoadRunnerMapTests:x86_64+0x10001eb3d) type_traits:3694
0x102fabaad std::__invoke_void_return_wrapper::__call<…> __functional_base:348
0x102faba5d std::__function::__alloc_func::operator() functional:1558
0x102fa9669 std::__function::__func::operator() functional:1732
0x102f9d383 std::__function::__value_func::operator() const functional:1885
0x102f9c055 std::function::operator() const functional:2560
0x102f9bc29 thread_pool::worker thread_pool.hpp:389 // ==> [this](https://github.com/bshoshany/thread-pool/blob/master/thread_pool.hpp#L389) line
0x102fa00bc (test_cxx_api_RoadRunnerMapTests:x86_64+0x1000130bc) type_traits:3635
0x102f9ff1e std::__thread_execute<…> thread:286
0x102f9f005 std::__thread_proxy<…> thread:297
0x1033e9a2c __tsan_thread_start_func
0x7fff204828fb _pthread_start
0x7fff2047e442 thread_start
Destructors are called in the order opposite to variable declaration order. i.e. v will be destructed earlier than pool, therefore at the moment when some threads from pool will call to printString(), the argument string will not be a valid object, because v and its content are already destroyed. To resolve this, I'd recommend to declare v before pool.
Tasks passed to thread pool contain references to content of vector v, however this vector goes out of scope prior to pool leaving tasks with dangling references. In order to fix this you need to reorder scopes of variables:
int N = 1000000;
std::vector<std::string> v(N);
thread_pool pool(16);

Segmentation Fault when assigning value to a pointer C++

When I run the following parallel code I get a segmentation fault at the assignment at row 18 (between the two prints). I don't really understand what is causing.
This is a minimal working example which describes the problem:
#include <iostream>
#include <numeric>
#include <vector>
#include <thread>
struct Worker{
std::vector<int>* v;
void f(){
std::vector<int> a(20);
std::iota(a.begin(), a.end(), 1);
auto b = new std::vector<int>(a);
std::cout << "Test 1" << std::endl;
v = b;
std::cout << "Test 2" << std::endl;
}
};
int main(int argc, char** argv) {
int nw = 1;
std::vector<std::thread> threads(nw);
std::vector<std::unique_ptr<Worker>> W;
for(int i = 0; i < nw; i++){
W.push_back(std::make_unique<Worker>());
threads[i] = std::thread([&]() { W[i]->f(); } );
// Pinning threads to cores
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(i, &cpuset);
pthread_setaffinity_np(threads[i].native_handle(), sizeof(cpu_set_t), &cpuset);
}
for (int i = 0; i < nw; i++) {
threads[i].join();
std::cout << (*(W[i]->v))[0] << std::endl;
}
}
It seems that compiling it with -fsanitize=address the code works fine but I get worst performances. How can I make it work?
std::vector is not thread-safe. None of the containers in the C++ library are thread safe.
threads[i] = std::thread([&]() { W[i]->f(); } );
The new execution thread captures the vector by reference and accesses it.
W.push_back(std::make_unique<Worker>());
The original execution thread continuously modifies the vector here, without synchronizing access to the W vector with any of the new execution threads. Any push_back may invalidate the existing contents of the vector in order to reallocate it, and if a different execution thread attempts to get W[i] at the same time, while it's being reallocated, hillarity ensues.
This is undefined behavior.
You must either synchronize access to the vector using a mutex, or make sure that the vector will never be reallocated, using any number of known techniques. A sufficiently-large reserve(), in advance, should do the trick.
Additionally, it's been pointed out that i is also captured by reference, so by the time each new execution thread starts, its value could be anything.
In addition to the vector synchronization problem mentioned by Sam, there is another problem.
This line:
threads[i] = std::thread([&]() { W[i]->f(); } );
captures i by reference. There is a good chance that i goes out of scope (and is destroyed) before the thread starts running. The statement W[i]->f(); is likely to read an invalid value of i which is negative or too large. Note that before i goes out of scope, the last value written to it is nw, so if even if the memory that previously contained i is still accessible, it's likely to have the value nw which is too large.
You could fix this problem by capturing i by value:
threads[i] = std::thread([&W, i]() { W[i]->f(); } );
// ^^^^^
// captures W by reference, and i by value
As noted by others, the capture is the problem.
I've added the i parameter to the f() call:
void f(int i){
std::vector<int> a(20);
std::iota(a.begin(), a.end(), 1);
auto b = new std::vector<int>(a);
std::cout << "Test 1 " << i << std::endl;
v = b;
std::cout << "Test 2 " << v->size() << std::endl;
}
and the output: Test 1 1
The call to f works however but it is called without a valid Worker instance and when you assign to v it is surely at a wrong memory.

Switching from global static variables to static variables breaks code

I'm working on an assignment for school, one of the requirements of which is that I cannot use global variables, but I do need static variables for shared memory. The premise of the assignment is to use the pthread library and semaphores to ensure that created threads execute in reverse order. I've gotten it to work with global static semaphore/condvar/mutex as such:
#include <pthread.h>
#include <stdio.h>
#include <iostream>
#include <semaphore.h>
using namespace std;
#define NUM 5
static sem_t threadCounter;
static pthread_cond_t nextThreadCond = PTHREAD_COND_INITIALIZER;
static pthread_cond_t makingThreadCond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t makingThreadMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t nextThreadMutex = PTHREAD_MUTEX_INITIALIZER;
void *wait_func(void *args)
{
// cout<<"Waiting"<<endl;
// pthread_cond_wait(&makingThreadCond, &makingThreadMutex);
// cout<<"Woke up"<<endl;
int tid = *((int *)args);
int val;
sem_getvalue(&threadCounter, &val);
// cout << tid << ":" << val << endl;
while (tid != val-1)
{
pthread_cond_wait(&nextThreadCond, &nextThreadMutex);
sem_getvalue(&threadCounter, &val);
// cout<<"evaluating condition in"<<tid<<", val is "<<val<<endl;
}
sem_wait(&threadCounter); // decrement threadCounter
// cout << "after decrement" << endl;
sem_getvalue(&threadCounter, &val);
// cout << "decremented val "<<val << endl;
cout<<"Exiting thread #"<<tid<<endl;
pthread_mutex_unlock(&nextThreadMutex);
// cout<<"after nextThreadMutex unlock"<<endl;
pthread_cond_broadcast(&nextThreadCond);
// cout<<"after nextThreadCond broadcast"<<endl;
}
int main()
{
pthread_t tid[NUM];
if (sem_init(&threadCounter, 0, NUM) < 0)
{
cout << "Failed to init sem" << endl;
}
for (int i = 0; i < NUM; i++)
{
int *argId = (int *)malloc(sizeof(*argId));
*argId = i;
if (pthread_create(&tid[i], NULL, wait_func, argId))
{
cout << "Couldn't make thread " << i << endl;
}
}
for (int i = 0; i < NUM; i++)
{
pthread_join(tid[i], NULL);
}
}
but this isn't allowed as I said, so I tried to convert it where I share them through a struct and passed in with pthread_create arguments as such:
#include <pthread.h>
#include <stdio.h>
#include <iostream>
#include <semaphore.h>
using namespace std;
#define NUM 5
struct args
{
int tid;
sem_t* sem;
pthread_cond_t* cond;
pthread_mutex_t* mut;
};
void *wait_func(void *args_ptr)
{
// cout<<"Waiting"<<endl;
// pthread_cond_wait(&makingThreadCond, &makingThreadMutex);
// cout<<"Woke up"<<endl;
struct args* args = (struct args*) args_ptr;
int tid = (args->tid);
pthread_cond_t cond = *(args->cond);
pthread_mutex_t mut = *(args->mut);
sem_t sem = *(args->sem);
int val;
sem_getvalue(&sem, &val);
// cout << tid << ":" << val << endl;
while (tid != val - 1)
{
pthread_cond_wait(&cond, &mut);
sem_getvalue(&sem, &val);
// cout<<"evaluating condition in"<<tid<<", val is "<<val<<endl;
}
sem_wait(&sem); // decrement threadCounter
// cout << "after decrement" << endl;
sem_getvalue(&sem, &val);
// cout << "decremented val "<<val << endl;
cout << "Exiting thread #" << tid << endl;
pthread_mutex_unlock(&mut);
// cout<<"after nextThreadMutex unlock"<<endl;
pthread_cond_broadcast(&cond);
// cout<<"after nextThreadCond broadcast"<<endl;
}
int main()
{
static sem_t threadCounter;
static pthread_cond_t nextThreadCond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t nextThreadMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_t tid[NUM];
if (sem_init(&threadCounter, 0, NUM) < 0)
{
cout << "Failed to init sem" << endl;
}
for (int i = 0; i < NUM; i++)
{
int *argId = (int *)malloc(sizeof(*argId));
*argId = i;
struct args args;
args.tid = *argId;
args.sem = &threadCounter;
args.cond = &nextThreadCond;
args.mut = &nextThreadMutex;
if (pthread_create(&tid[i], NULL, wait_func, &args))
{
cout << "Couldn't make thread " << i << endl;
}
}
// cout << "Before posting sem" << endl;
// sem_post(&makingThreads);
// cout << "Sem posetd" << endl;
// cout<<"Broadcasting"<<endl;
// pthread_cond_broadcast(&makingThreadCond);
for (int i = 0; i < NUM; i++)
{
pthread_join(tid[i], NULL);
}
}
This gets stuck immediately with "Exiting thread #4" twice. I would think that the second code is equivalent to the first, just without global variables but there must be something I'm missing.
struct args args;
This declares an object inside the scope of your for loop. When execution reaches the end of the for loop, this object gets destroyed -- like any other object that's declared locally within a function or within some inner scope -- and this happens before either the loop starts again from the beginning, or if the for loop stops iterating altogether. Either way, as soon the execution reaches the next } this object goes away. It is gone for good. It gets destroyed. It is no more. It joins the choir-invisible. It becomes an ex-object.
But before that happens, before the end of this loop, the following occurs:
if (pthread_create(&tid[i], NULL, wait_func, &args))
So you start a new execution thread, and pass it a pointer to this object, which is about to meet its maker.
And as soon as pthread_create() returns, that's the end of the loop and your args object is gone, and the abovementioned happens: it gets destroyed; it is no more; it joins the choir-invisible; and it becomes an ex-object.
And the C and the C++ standards give you absolutely no guarantees whatsoever, that your new execution thread actually starts running, and reaches the point where it reads this pointer, and what it's pointing to, before the end of this loop gets reached.
And, more likely than not, each new execution thread doesn't get around to reading the pointer to the args object, in the main execution thread, until long after it gets destroyed. So it grabs stuff from a pointer to a destroyed object. Goodbye.
As such, this execution thread's actions become undefined behavior.
This explains the random, unpredictable behavior that you've observed.
The usual approach is to malloc or new everything that gets passed to your new execution thread, and pass to the execution thread a pointer to the newed or malloced object.
It is also possible to carefully write some code that will make the main execution thread stop and wait until the new execution thread retrieves whatever it needs to do, and then proceeds on its own. A bunch more code will be needed to implement that approach, if you so choose.
Your code also has evidence of your initial attempts to take this approach:
int *argId = (int *)malloc(sizeof(*argId));
*argId = i;
struct args args;
args.tid = *argId;
mallocing this pointer, assigning to it, then copying it to args.tid accomplishes absolutely nothing useful. The same thing can be done simply by:
struct args args;
args.tid = i;
The only thing that malloc does is leak memory. Furthermore, this whole args object, declared as a local variable in the for loop's inner scope, is doomed for the reasons explained above.
P.S. When taking the "malloc the entire args object" approach, this also will leak memory unless you also take measures to diligently free the malloced object, when it is appropriate to do so.
You are passing a pointer to the local variable args to pthread_create. The variable's lifetime ends when the for loop iteration ends and the pointer becomes dangling.
The thread may be accessing it later though, causing undefined behavior.
You need to allocate args dynamically (but not argId), and pass that to the thread. The thread function must then assure the deletion of the pointer. Also don't name your variables the same thing as a type. That is very confusing. The struct keyword in a variable declaration is generally (if you don't name variables and types the same) not needed in C++ and may cause other issues when used without reason, so don't use it and name thing differently.
struct Args
{
int tid;
sem_t* sem;
pthread_cond_t* cond;
pthread_mutex_t* mut;
};
//...
auto args = new Args{i, &threadCounter, &nextThreadCond, &nextThreadMutex};
if (pthread_create(&tid[i], NULL, wait_func, args))
{
cout << "Couldn't make thread " << i << endl;
}
and at the end of the thread function delete the pointer:
void *wait_func(void *args_ptr)
{
auto args = static_cast<Args*>(args_ptr);
//...
delete args;
}
static_cast is safer than the C style cast, since it is much more restricted in the types it can cast between and e.g. can't accidentally drop a const or anything similar.
None of the variables seem to have a reason to be static either in the global or local case.
pthread_cond_t cond = *(args->cond);
pthread_mutex_t mut = *(args->mut);
This tries to create a new condition variable and mutex and initialize it based on the value of the condition variable and mutex pointed to. That doesn't make sense and won't work.
while (tid != val - 1)
{
pthread_cond_wait(&cond, &mut);
sem_getvalue(&sem, &val);
// cout<<"evaluating condition in"<<tid<<", val is "<<val<<endl;
Here, you pass to pthread_cond_wait a pointer to the local condition variable and mutex you created above rather than a pointer to the shared one. Look at this code:
int a;
foo(&a);
void foo(int* a)
{
int b = *a;
bar (&b); // If bar changes *b, that will not affect a!
}
See the problem? You passed bar a pointer to b, not a. So if bar changes the thing the pointer points to, it won't be modifying a but the local copy of b.
Don't try to create mutexes or condition variables that are copies of other mutexes or condition variables. It doesn't make semantic sense and it won't work.
Instead, you can do this:
pthread_cond_t* cond = (args->cond);
pthread_mutex_t* mut = (args->mut);
Now you can pass cond and mut to pthread_cond_wait, and you'll be passing pointers to the shared synchronization objects.

C++ vector erase iterator out of range due to mutexes

So, there is a vector of strings. Since its a static member of cl_mgr class, it acts as global variable.
std::vector<std::string> cl_mgr::to_send_queue;
However, i dont ever directly access this vector in my code. To add strings to it i call following function:
void cl_mgr::sendmsg(std::string msg)
{
std::mutex mtx;
mtx.lock();
if ( connected )
{
cl_mgr::to_send_queue.push_back(msg + '\r');
}
mtx.unlock();
}
This is where it goes wrong: the line
cl_mgr::to_send_queue.erase(cl_mgr::to_send_queue.begin());
sometimes gives iterator out of range.
This should only happen when vector is empty, but i already check for this in while condition.
So next i added sizes array to fill it with to_send_queue.size() and found out sometimes it returns zero ! Usually all array consists of 1's, but sometimes an element like sizes[9500] is a 0.
Whats wrong and how to fix this ?
std::mutex mtx;
mtx.lock();
while ( !cl_mgr::to_send_queue.empty() )
{
string tosend = cl_mgr::to_send_queue[0];
int sizes[10000];
sizes[0]=0;
for (int i = 1; i < 10000; ++i)
{
sizes[i] = cl_mgr::to_send_queue.size();
if ( sizes[i] < sizes[i-1] )
{
int breakpoint = 0; //should never be hit but it does !
}
}
cl_mgr::to_send_queue.erase(cl_mgr::to_send_queue.begin()); //CRASH HERE
send(hSocket, tosend.c_str(), tosend.length(), 0 );
Sleep(5);
}
mtx.unlock();
This std::mutex is local to the method. This means every invocation of this method has it's own mutex and doesn't protect anything.
To fix this, you must move the mutex to the same scope as the vector to_send_queue and use a std::lock_guard. At the website, there is an example how to use this
int g_i = 0;
std::mutex g_i_mutex; // protects g_i
void safe_increment()
{
std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
// g_i_mutex is automatically released when lock
// goes out of scope
}