Trying to switch between threads using condition_variables - c++

I have two threads. Both get initialized, but the first thread runs first, while the second thread waits for the first thread (call_once(flagEven1, [&](){cond2.wait(lock, [&](){return zeroEvenOddFlag == 1; }); });). After a certain condition is met, the first thread wakes up the second thread and goes to sleep.
zeroEvenOddFlag = 0;
cond1.notify_all();
cond2.wait(lock, [&](){return zeroEvenOddFlag == 1; });
This process alternates until the task is finished. Right now the threads just get locked up and I don't know what's happening. The threads together should process the input string and produce two strings of zeros and non-zero numbers. I corrected for spurious wakeups by adding an integer that is shared between the threads (zeroEvenOddFlag).
#include <string>
#include <thread>
#include <future>
#include <cstdio>
#include <iostream>
#include <queue>
#include <condition_variable>
using namespace std;
mutex mu;
condition_variable cond1;
condition_variable cond2;
once_flag flagZero;
once_flag flagEven;
once_flag flagEven1;
int zeroEvenOddFlag = 0;
string printerFunc(queue<char>& input, int zeroEvenOdd, once_flag& flag){
string output = "";
function<void(string&)> f;
unique_lock<mutex> lock(mu);
call_once(flag, [&](){
if (zeroEvenOdd == 1){
f = [&](string& output){
int i = input.front() - '0';
if (i == 0){
output += to_string(i);
input.pop();
}
else {
zeroEvenOddFlag = 1;
cond2.notify_all();
cond1.wait(lock, [&](){return zeroEvenOddFlag == 0; });
}
};
}
else if (zeroEvenOdd == 2){
call_once(flagEven1, [&](){cond2.wait(lock, [&](){return zeroEvenOddFlag == 1; }); });
f = [&](string& output){
int i = input.front() - '0';
if (i != 0){
output += to_string(i);
input.pop();
}
else {
zeroEvenOddFlag = 0;
cond1.notify_all();
cond2.wait(lock, [&](){return zeroEvenOddFlag == 1; });
}
};
}
});
while(input.size()!=0){
f(output);
}
return output;
}
int _tmain(int argc, _TCHAR* argv[])
{
string input = "0102030";
queue<char> inputQueue;
for(char c : input){
inputQueue.push(c);
}
auto zeros = async(launch::async, printerFunc, ref(inputQueue), 1, ref(flagZero));
auto evens = async(launch::async, printerFunc, ref(inputQueue), 2, ref(flagEven));
string zerosString = zeros.get();
string evensString = evens.get();
cout << "these are the zeros " << zerosString << endl;
cout << "these are the evens " << evensString << endl;
this_thread::sleep_for(chrono::seconds(10));
return 0;
}

You need to always check for spurious wake-ups conditions. The use of cond2.wait(lock, [&](){return zeroEvenOddFlag == 1; }); and the like help check. Also, you reach an issue at the end of your loop. Which ever thread finally owns the mutex has to release it when it consumes the last bit of the queue, so that the other threads can be freed up to finish. The final code should look like this:
#include <string>
#include <thread>
#include <future>
#include <cstdio>
#include <iostream>
#include <queue>
#include <condition_variable>
using namespace std;
mutex mu;
condition_variable cond1;
condition_variable cond2;
once_flag flagZero;
once_flag flagEven;
once_flag flagEven1;
int zeroEvenOddFlag = 0;
string printerFunc(queue<char>& input, int zeroEvenOdd, once_flag& flag){
string output = "";
function<void(string&)> f;
unique_lock<mutex> lock(mu);
call_once(flag, [&](){
if (zeroEvenOdd == 1){
f = [&](string& output){
int i = input.front() - '0';
if (i == 0){
output += to_string(i);
input.pop();
if (input.size() == 0){
zeroEvenOddFlag = 1;
cond2.notify_all();
}
}
else {
zeroEvenOddFlag = 1;
cond2.notify_all();
cond1.wait(lock, [&](){return zeroEvenOddFlag == 0; });
}
};
}
else if (zeroEvenOdd == 2){
call_once(flagEven1, [&](){cond2.wait(lock, [&](){return zeroEvenOddFlag == 1; }); });
f = [&](string& output){
int i = input.front() - '0';
if (i != 0){
output += to_string(i);
input.pop();
if (input.size() == 0){
zeroEvenOddFlag = 0;
cond1.notify_all();
}
}
else {
zeroEvenOddFlag = 0;
cond1.notify_all();
cond2.wait(lock, [&](){return zeroEvenOddFlag == 1; });
}
};
}
});
while(input.size()!=0){
f(output);
}
return output;
}
int _tmain(int argc, _TCHAR* argv[])
{
string input = "0102030";
queue<char> inputQueue;
for(char c : input){
inputQueue.push(c);
}
auto zeros = async(launch::async, printerFunc, ref(inputQueue), 1, ref(flagZero));
auto evens = async(launch::async, printerFunc, ref(inputQueue), 2, ref(flagEven));
string zerosString = zeros.get();
string evensString = evens.get();
cout << "these are the zeros " << zerosString << endl;
cout << "these are the evens " << evensString << endl;
this_thread::sleep_for(chrono::seconds(10));
return 0;
}

Related

Writing in file from shared buffer missing data and program crash without cout

I am making a program using threads and a shared buffer. The two threads run indefinitely in the background, one thread will fill a shared buffer with data and the other thread will write the content of the shared buffer into a file.
The user can start or stop the data filling which is resulting in the thread entering into a waiting state until the user starts the thread again. Each loop the buffer is filled with 50 floats.
This is the code :
#include <iostream>
#include <vector>
#include <iterator>
#include <utility>
#include <fstream>
#include <condition_variable>
#include <mutex>
#include <thread>
using namespace std;
std::mutex m;
std::condition_variable cv;
std::vector<std::vector<float>> datas;
bool keep_running = true, start_running = false;
void writing_thread()
{
ofstream myfile;
bool opn = false;
while(1)
{
while(keep_running)
{
// Open the file only once
if(!opn)
{
myfile.open("IQ_Datas.txt");
opn = true;
}
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] {return !datas.empty();});
auto d = std::move(datas);
lk.unlock();
for(auto &entry : d)
{
for(auto &e : entry)
myfile << e << endl;
}
}
if(opn)
{
myfile.close();
opn = false;
}
}
}
void sending_thread()
{
std::vector<float> m_buffer;
int cpt=0;
//Fill the buffer with 50 floats
for(float i=0; i<50; i++)
m_buffer.push_back(i);
while(1)
{
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] {return keep_running && start_running;});
}
while(keep_running)
{
//Each loop d is containing 50 floats
std::vector<float> d = m_buffer;
cout << "in3" << endl; //Commenting this line makes the program crash
{
std::lock_guard<std::mutex> lk(m);
if (!keep_running)break;
datas.push_back(std::move(d));
}
cv.notify_one();
cpt++;
}
cout << "Total data: " << cpt*50 << endl;
cpt = 0;
}
}
void start()
{
{
std::unique_lock<std::mutex> lk(m);
start_running = true;
}
cv.notify_all();
}
void stop()
{
{
std::unique_lock<std::mutex> lk(m);
start_running = false;
}
cv.notify_all();
}
int main()
{
int go = 0;
thread t1(sending_thread);
thread t2(writing_thread);
t1.detach();
t2.detach();
while(1)
{
std::cin >> go;
if(go == 1)
{
start();
keep_running = true;
}
else if(go == 0)
{
stop();
keep_running = false;
}
}
return 0;
}
I have 2 issues with this code :
When commenting the line cout << "in3" << endl; the program will crash after ~20-40 seconds with the error message : terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc. If i let the cout, the program will run without problems.
When the program is working, after stoping sending_thread i display the total amount of data that has been copied with cout << "Total data: " << cpt*50 << endl;. For small amount of datas, all of it is written correctly into the file but when the amount is big, there is missing data. Missing/Correct data (Total number of lines in the file does not match total data)
Why with the cout the program is running correctly ? And what is causing the missing data ? Is it because sending_thread is filling the buffer too fast while writing_threadtakes too much time to write into the file?
EDIT: Some precisions, adding more cout into sending_threadseems to fix all the issues. First thread produced 21 million floats and second thread successfully wrote in the file 21 million floats. It seems like without the cout, producer threads works too fast for the consumer thread to keep retrieving data from the shared buffer while writing it into a file.
To avoid:
Moved-from object 'datas' of type 'std::vector' is moved:
auto d = std::move(datas);
^~~~~~~~~~~~~~~~
Replace this:
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] {return !datas.empty();});
auto d = std::move(datas);
lk.unlock();
With this:
// Wait until main() sends data
std::vector<std::vector<float>> d;
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return !datas.empty(); });
datas.swap(d);
}
Also replace your bool variables that are accessed from multiple threads with std::atomic_bool or std::atomic_flag.
The bad_alloc comes from sending_thread being much faster than writing_thread so it will run out of memory. When you slow down sending_thread enough (with printing), the problem is less visible, but you should have some synchronization to do it properly. You could make a wrapper class around it and provide insert and extraction methods to make sure all access is synchronized properly and also give it a max number of elements. An example:
template<typename T>
class atomic2dvector {
public:
atomic2dvector(size_t max_elements) : m_max_elements(max_elements) {}
atomic2dvector(const atomic2dvector&) = delete;
atomic2dvector(atomic2dvector&&) = delete;
atomic2dvector& operator=(const atomic2dvector&) = delete;
atomic2dvector& operator=(atomic2dvector&&) = delete;
~atomic2dvector() { shutdown(); }
bool insert_one(std::vector<T>&& other) {
std::unique_lock<std::mutex> lock(m_mtx);
while(m_current_elements + m_data.size() > m_max_elements && m_shutdown == false)
m_cv.wait(lock);
if(m_shutdown) return false;
m_current_elements += other.size();
m_data.emplace_back(std::forward<std::vector<T>>(other));
m_cv.notify_one();
return true;
}
std::vector<std::vector<T>> extract_all() {
std::vector<std::vector<T>> return_value;
std::unique_lock<std::mutex> lock(m_mtx);
while(m_data.empty() && m_shutdown == false) m_cv.wait(lock);
if(m_shutdown == false) {
m_current_elements = 0;
return_value.swap(m_data);
} else {
// return an empty vector if we should shutdown
}
m_cv.notify_one();
return return_value;
}
bool is_active() const { return m_shutdown == false; }
void shutdown() {
m_shutdown = true;
m_cv.notify_all();
}
private:
size_t m_max_elements;
size_t m_current_elements = 0;
std::atomic<bool> m_shutdown = false;
std::condition_variable m_cv{};
std::mutex m_mtx{};
std::vector<std::vector<T>> m_data{};
};
If you'd like to keep extracting data even after shutdown, you can change extract_all() to this:
std::vector<std::vector<T>> extract_all() {
std::vector<std::vector<T>> return_value;
std::unique_lock<std::mutex> lock(m_mtx);
while(m_data.empty() && m_shutdown == false) m_cv.wait(lock);
m_current_elements = 0;
return_value.swap(m_data);
m_cv.notify_one();
return return_value;
}
A full example could look like this:
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <fstream>
#include <iostream>
#include <iterator>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
using namespace std;
template<typename T>
class atomic2dvector {
public:
atomic2dvector(size_t max_elements) : m_max_elements(max_elements) {}
atomic2dvector(const atomic2dvector&) = delete;
atomic2dvector(atomic2dvector&&) = delete;
atomic2dvector& operator=(const atomic2dvector&) = delete;
atomic2dvector& operator=(atomic2dvector&&) = delete;
~atomic2dvector() { shutdown(); }
bool insert_one(std::vector<T>&& other) {
std::unique_lock<std::mutex> lock(m_mtx);
while(m_current_elements + m_data.size() > m_max_elements &&
m_shutdown == false)
m_cv.wait(lock);
if(m_shutdown) return false;
m_current_elements += other.size();
m_data.emplace_back(std::forward<std::vector<T>>(other));
m_cv.notify_one();
return true;
}
std::vector<std::vector<T>> extract_all() {
std::vector<std::vector<T>> return_value;
std::unique_lock<std::mutex> lock(m_mtx);
while(m_data.empty() && m_shutdown == false) m_cv.wait(lock);
m_current_elements = 0;
return_value.swap(m_data);
m_cv.notify_one();
return return_value;
}
bool is_active() const { return m_shutdown == false; }
void shutdown() {
m_shutdown = true;
m_cv.notify_all();
}
private:
size_t m_max_elements;
size_t m_current_elements = 0;
std::atomic<bool> m_shutdown = false;
std::condition_variable m_cv{};
std::mutex m_mtx{};
std::vector<std::vector<T>> m_data{};
};
std::mutex m;
std::condition_variable cv;
atomic2dvector<float> datas(256 * 1024 * 1024 / sizeof(float)); // 0.25 GiB limit
std::atomic_bool start_running = false;
void writing_thread() {
std::ofstream myfile("IQ_Datas.txt");
if(myfile) {
std::cout << "writing_thread waiting\n";
std::vector<std::vector<float>> d;
while((d = datas.extract_all()).empty() == false) {
std::cout << "got " << d.size() << "\n";
for(auto& entry : d) {
for(auto& e : entry) myfile << e << "\n";
}
std::cout << "wrote " << d.size() << "\n\n";
}
}
std::cout << "writing_thread shutting down\n";
}
void sending_thread() {
std::vector<float> m_buffer;
std::uintmax_t cpt = 0;
// Fill the buffer with 50 floats
for(float i = 0; i < 50; i++) m_buffer.push_back(i);
while(true) {
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] {
return start_running == true || datas.is_active() == false;
});
}
if(datas.is_active() == false) break;
std::cout << "sending...\n";
while(start_running == true) {
// Each loop d is containing 50 floats
std::vector<float> d = m_buffer;
if(datas.insert_one(std::move(d)) == false) break;
cpt++;
}
cout << "Total data: " << cpt * 50 << endl;
cpt = 0;
}
std::cout << "sending_thread shutting down\n";
}
void start() {
std::unique_lock<std::mutex> lk(m);
start_running = true;
cv.notify_all();
}
void stop() {
std::unique_lock<std::mutex> lk(m);
start_running = false;
cv.notify_all();
}
void quit() {
datas.shutdown();
cv.notify_all();
}
int main() {
int go = 0;
thread t1(sending_thread);
thread t2(writing_thread);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Enter 1 to make the sending thread send and 0 to make it stop "
"sending. Enter a non-integer to shutdown.\n";
while(std::cin >> go) {
if(go == 1) {
start();
} else if(go == 0) {
stop();
}
}
std::cout << "--- shutting down ---\n";
quit();
std::cout << "joining threads\n";
t1.join();
std::cout << "t1 joined\n";
t2.join();
std::cout << "t2 joined\n";
}

C++ condition_variable wait_for not always working

My application start the logThread thread and then cyclically calls lookForSms wich makes a curl call and wait for logThread to read a certain string, but this string may never come so i used a condition_variable wait_for so that eventually (after 5 minutes) it can continue the test.
The problem is at times it waits a lot longer, sometimes 20 min sometimes forever.
How can I make it reliably work?
I'm using Visual Studio Community 2017, here the code:
void lookForSms(CURL *curl) {
sms_found = false;
int i;
CURLcode res;
cout << "\n" << timestamp() << "\t";
res = curl_easy_perform(curl);
if (res == CURLE_OK) {
unique_lock<mutex> lk(F);
analysis << timestampMil() << "\t";
while (cv.wait_for(lk, TIMEOUT_SMS * 1000ms) != cv_status::timeout && !sms_found);
if (sms_found) {
analysis << timestampMil() << "\tSMS RECEIVED\n";
cout << "\tOK";
}
else {
analysis << timestampMil() << "\tSMS LOST\n";
cout << "\tKO";
}
}
}
void logThread() {
string line;
int n, i, count;
unsigned char buf[BUFFER];
while (!close) {
N.lock();
M.lock();
N.unlock();
n = RS232_PollComport(comport, buf, BUFFER);
if (n > 0 && n < BUFFER) {
buf[n] = '\0';
for (i = 0; i < n; i++) {
if (buf[i] == '\n') {
all << timestampMil() << "\t" << line << "\n";
handleLine(line);
line = string();
}
else if (buf[i] != '\r' && buf[i] >= 32) {
line += buf[i];
}
}
}
M.unlock();
}
}
void handleLine(string line) {
if (searchString(line, RCV_SMS)) {
unique_lock<mutex> lk(F);
sms_found = true;
lk.unlock();
cv.notify_all();
}
}
[EDIT] This should have everything it needs to recreate the problem.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#define TIMEOUT_SMS 300
#define DELAY_SMS 10
mutex L, M, N, F;
condition_variable cv;
void logThread() {
while (!close) {
N.lock();
M.lock();
N.unlock();
handleLine("");
Sleep(500);
M.unlock();
}
}
void lookForSms() {
unique_lock<mutex> lk(F);
sms_found = false;
unlocked = false;
//while (cv.wait_for(lk, TIMEOUT_SMS * 1000ms) != cv_status::timeout && !unlocked);
cv.wait_for(lk, 10 * TIMEOUT_SMS * 1000ms, []() {return unlocked; });
if (sms_found) {
cout << "\tOK";
}
else {
cout << "\tKO";
}
}
}
void handleLine(string line) {
if (false) {// the problem is when cv remains locked
unique_lock<mutex> lk(F);
unlocked = true;
sms_found = true;
lk.unlock();
cv.notify_all();
}
}
void stopThreads() {
N.lock();
M.lock();
//stop log thread
close = true;
N.unlock();
M.unlock();
}
int main(int argc, const char * argv[]) {
thread logger(logThread);
for (i = 0; i < repeat; i++) {
lookForSms();
Sleep(DELAY_SMS * 1000);
}
//aquire mutex and set close to true, then join
stopThreads();
//wait for the thread to end
logger.join();
return 0;
}

Producers, Consumers, and my cute Deadlock

I'm working on a Producer Consumer (Multithreaded) problem and I'm encountering a deadlock. My question is how?
I have multiple producers creating n products, and putting them in a global queue. The producers must WAIT if there is no room in the queue.
My consumers are accessing the queue by using First Come First Serve. The consumer must WAIT if there is nothing in the queue. The consumer will consume PART of the product, and the product will only be removed from the queue when it has been totally consumed. The consumers will stop consuming when there are no more products to be consumed.
I get the deadlock when the first item is done being consumed. I am using a mutex to lock the queue, 2 condition variables to signal when an item is added or removed to the queue, and a semaphore to keep track of total items being consumed.
Any idea why it may be deadlocking?
Input: 2 (Producers) 3 (Consumers) 5 (Items to produce) 2 (Queue Size) 0 (N/A) 50 (Quantum) 1 (Seed)
Producer:
#include <pthread.h>
#include <semaphore.h>
#include "producerConsumer.h"
#include <iostream>
#include <sys/time.h>
void* producer(void* args)
{
tProducer producer = *((tProducer*) args);
tProduct products[producer.products];
unsigned int queueMax = producer.queueMax;
timeval time;
std::string output;
for(int i = 0; i < producer.products; i++)
{
//Create item
products[i].productId = i;
products[i].life = producer.lifeOfProduct;
gettimeofday(&time, NULL);
products[i].timestamp = time.tv_sec;
//Lock and add to queue
pthread_mutex_lock(&queueLock);
//Queue is full and must wait
if(queue.size() >= queueMax)
{
output = "Producer: " + std::to_string(producer.id) + " is waiting\n";
std::cout << output;
pthread_cond_wait(&removeSignal, &queueLock);
}
//Debug message
output = "Producer: " + std::to_string(producer.id) + " is producing.\n";
std::cout << output;
//Add item to queue and signal
queue.push(products[i]);
pthread_cond_signal(&addSignal);
pthread_mutex_unlock(&queueLock);
//pthread_cond_signal(&addSignal);
//Debug message
output = "Producer: " + std::to_string(producer.id) + " just produced.\n";
std::cout << output;
}
pthread_exit(NULL);
}
Consumer:
#include <pthread.h>
#include <semaphore.h>
#include "producerConsumer.h"
#include <iostream>
void* consumer(void* args)
{
tConsumer consumer = *((tConsumer*) args);
int id = consumer.id;
int quantum = consumer.quantum;
std::string output;
while(true)
{
//Exit when nothing else is being created
if(sem_trywait(&totalProductsLeft) < 0)
{
break;
}
//Subtract life from product, and remove from queue if done
pthread_mutex_lock(&queueLock);
//Wait until item is in queue
if(queue.size() <= 0)
{
//Debug message
output = "Consumer: " + std::to_string(id) + " is waiting.\n";
std::cout << output;
pthread_cond_wait(&addSignal, &queueLock);
}
//Debug message
output = "Consumer: " + std::to_string(id) + " is ready.\n";
std::cout << output;
tProduct& product = queue.front();
product.life -= quantum;
//Item is done being consumed
if(product.life <= 0)
{
//Debug message
output = "Product: " + std::to_string(product.productId) + " is dead.\n";
std::cout << output;
//Remove a spot
queue.pop();
pthread_cond_signal(&removeSignal);
sem_wait(&totalProductsLeft);
}
else
{
//Debug message
output = "Product: " + std::to_string(product.life) + "hp is not done.\n";
std::cout << output;
}
pthread_mutex_unlock(&queueLock);
}
//May need to broadcast
pthread_exit(NULL);
}
Main (Just to show how I initialize everything):
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <pthread.h>
#include <semaphore.h>
#include "producerConsumer.h"
std::queue<tProduct> queue;
pthread_cond_t addSignal;
pthread_cond_t removeSignal;
sem_t totalProductsLeft;
pthread_mutex_t queueLock;
int main(int argc, char** argv)
{
//Handle input
const int NUM_INPUTS = 8;
int numberProducers;
int numberConsumers;
int numberOfProducts;
int queueSize;
int scheduleType;
int quantum;
int seed;
//Error check for input
if(argc != NUM_INPUTS)
{
std::cout << "Invalid number of arguments.\n";
return -1;
}
//Grab arguments
numberProducers = atoi(argv[1]);
numberConsumers = atoi(argv[2]);
numberOfProducts = atoi(argv[3]);
queueSize = atoi(argv[4]);
scheduleType = atoi(argv[5]);
quantum = atoi(argv[6]);
seed = atoi(argv[7]);
//Get rid of warnings for now
std::cout << numberOfProducts << std::endl;
std::cout << queueSize << std::endl;
std::cout << quantum << std::endl;
std::cout << seed << std::endl;
std::cout << scheduleType << std::endl;
//Create threads
pthread_t producerThreads[numberProducers];
pthread_t consumerThreads[numberConsumers];
tProducer producerArgs[numberProducers];
tConsumer consumerArgs[numberConsumers];
//Initialize global
pthread_mutex_init(&queueLock, NULL);
pthread_cond_init(&addSignal, NULL);
pthread_cond_init(&removeSignal, NULL);
std::cout << "Total Items: " << (numberProducers * numberOfProducts) << std::endl;
sem_init(&totalProductsLeft, 0, numberProducers * numberOfProducts);
//Start threads
srand(seed);
for(int i = 0; i < numberProducers; i++)
{
producerArgs[i].id = i;
producerArgs[i].products = numberOfProducts;
producerArgs[i].lifeOfProduct = rand() % 1024;
producerArgs[i].queueMax = queueSize;
pthread_create(&(producerThreads[i]), 0, producer, &producerArgs[i]);
}
for(int i = 0; i < numberConsumers; i++)
{
consumerArgs[i].id = i;
consumerArgs[i].quantum = quantum;
pthread_create(&(consumerThreads[i]), 0, consumer, &consumerArgs[i]);
}
//Wait for threads to end
for(int i = 0; i < numberProducers; i++)
{
pthread_join(producerThreads[i], NULL);
}
for(int i = 0; i < numberConsumers; i++)
{
pthread_join(consumerThreads[i], NULL);
}
return 0;
}
I ended up figuring it out. sem_trywait, in my consumer, is decrementing when items weren't done being consumed. The sem_wait inside my consumer is then blocking because there are no items left.

synchronization issue with 3 threads and 2 shared resources in c++11

i have 3 thread and 2 shared resources, which need some locking...i tried to illustrate the resources with 2 buffers...
- thread 1 can only access resource 1
- thread 2 can access resource 1 and 2
- thread 3 can access resource 1 and 2
can someone tell me why the following locking fails? since thread2 and thread3 will access resource 1 and 2...i thought i could use try_lock? ...it seems the issue pops up, when thread2 and thread3 is only able to lock 1 mutex at a time...any idea?
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <algorithm>
#include <cassert>
using namespace std;
class SynchronizationTest {
private:
mutex lock_r1;
mutex lock_r2;
vector<pair<string, int>> buffer_r1;
vector<pair<string, int>> buffer_r2;
unsigned int buffer_r1_max_size;
unsigned int buffer_r2_max_size;
bool buffer_r1_inc_element(const string &thread_id) {
if (buffer_r1.size() == buffer_r1_max_size) {
return true;
}
if (buffer_r1.size() == 0) {
buffer_r1.push_back(make_pair(thread_id, 0));
}
else {
int last_val = buffer_r1.back().second;
buffer_r1.push_back(make_pair(thread_id, ++last_val));
}
return false;
}
bool buffer_r2_inc_element(const string &thread_id) {
if (buffer_r2.size() == buffer_r2_max_size) {
return true;
}
if (buffer_r2.size() == 0) {
buffer_r2.push_back(make_pair(thread_id, 0));
}
else {
int last_val = buffer_r2.back().second;
buffer_r2.push_back(make_pair(thread_id, ++last_val));
}
return false;
}
public:
SynchronizationTest(int buff_r1_size, int buff_r2_size) : buffer_r1_max_size(buff_r1_size),
buffer_r2_max_size(buff_r2_size) {}
void thread1() {
bool buffer_r1_full = false;
while (!buffer_r1_full) {
{
unique_lock<mutex> l(lock_r1, std::defer_lock);
if (l.try_lock()) {
buffer_r1_full = buffer_r1_inc_element("thread1");
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void thread2() {
bool buffer_r1_full = false;
bool buffer_r2_full = false;
while (!buffer_r1_full || !buffer_r2_full) {
{
unique_lock<mutex> lock1(lock_r1, defer_lock);
unique_lock<mutex> lock2(lock_r2, defer_lock);
int result = try_lock(lock1, lock2);
if(result == -1) {
buffer_r1_full = buffer_r1_inc_element("thread2");
buffer_r2_full = buffer_r2_inc_element("thread2");
}
else if(result != 0) {
buffer_r1_full = buffer_r1_inc_element("thread2");
}
else if(result != 1) {
buffer_r2_full = buffer_r2_inc_element("thread2");
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void thread3() {
bool buffer_r1_full = false;
bool buffer_r2_full = false;
while (!buffer_r1_full || !buffer_r2_full) {
{
unique_lock<mutex> lock1(lock_r1, defer_lock);
unique_lock<mutex> lock2(lock_r2, defer_lock);
int result = try_lock(lock1, lock2);
if(result == -1) {
buffer_r1_full = buffer_r1_inc_element("thread3");
buffer_r2_full = buffer_r2_inc_element("thread3");
}
else if(result != 0) {
buffer_r1_full = buffer_r1_inc_element("thread3");
}
else if(result != 1) {
buffer_r2_full = buffer_r2_inc_element("thread3");
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void print_buffer() {
for_each(buffer_r1.begin(), buffer_r1.end(), [](pair<string, int> p) { cout << p.first.c_str() << " " << p.second << endl; });
cout << '\n';
for_each(buffer_r2.begin(), buffer_r2.end(), [](pair<string, int> p) { cout << p.first.c_str() << " " << p.second << endl; });
}
};
int main() {
// your code goes here
SynchronizationTest st(20, 20);
thread t1(&SynchronizationTest::thread1, &st);
thread t2(&SynchronizationTest::thread2, &st);
thread t3(&SynchronizationTest::thread3, &st);
t1.join();
t2.join();
t3.join();
st.print_buffer();
return 0;
}
std::try_lock does not work that way. If it returns -1, all locks are held. If it returns a non-negative integer, no locks are held. The returned value tells which lock failed, but any locks that were locked successfully are released before try_lock returns.
problem solved:
unique_lock<mutex> lock1(lock_r1, defer_lock);
unique_lock<mutex> lock2(lock_r2, defer_lock);
bool result1 = lock1.try_lock();
bool result2 = lock2.try_lock();
if(result1 && result2) {
buffer_r1_full = buffer_r1_inc_element("thread2");
buffer_r2_full = buffer_r2_inc_element("thread2");
}
else if(result1) {
buffer_r1_full = buffer_r1_inc_element("thread2");
}
else if(result2) {
buffer_r2_full = buffer_r2_inc_element("thread2");
}

the magic cout that keep my program alive

I wrote a code to implement spin lock and mutex lock.
There is an interesting but. A magic cout can keep my program alive. If I remove the cout, my program will be sleeping forever. (This only happens in Linux. Windows is doing fine)
Any one have a clue?
#include <pthread.h>
#include <iostream>
#include <queue>
#include <sys/time.h>
#include <stdexcept>
#include <cstdio>
#include <cstdlib>
using namespace std;
#define Tcount 10
#define TheLock MutexLock
static inline int TAS(volatile int * ptr) {
unsigned long result;
asm volatile("lock;"
"xchgl %0, %1;"
: "=r"(result), "=m"(*ptr)
: "0"(1), "m"(*ptr)
: "memory");
return result;
}
class SpinLock {
private:
int lock;
pthread_t owner;
public:
SpinLock() {
lock = 0;
}
void getLock() {
while (TAS(&lock) == 1) {
}
owner = pthread_self();
}
void releaseLock() {
if (lock == 0) {
cout << "Spin no lock" << endl;
return;
} else if (owner == pthread_self()) {
owner = NULL;
lock = 0;
} else {
throw runtime_error("Spin can't release");
}
}
};
class MutexLock {
private:
int lock;
pthread_t owner;
queue<pthread_t> q;
SpinLock qLock;
public:
MutexLock() {
lock = 0;
}
void getLock(int id) {
pthread_t self = pthread_self();
cout<<"a"<<endl;// magic cout
if (TAS(&lock) == 0) {
owner = self;
return;
}
qLock.getLock();
q.push(self);
qLock.releaseLock();
while (owner != self) {
}
}
void releaseLock(int id) {
if (lock == 0) {
cout << "Mutex no lock" << endl;
return;
} else if (owner == pthread_self()) {
qLock.getLock();
if (q.empty()) {
owner = NULL;
lock = 0;
} else {
owner = q.front();
q.pop();
}
qLock.releaseLock();
} else {
throw runtime_error("Mutex can't release");
}
}
};
TheLock lock;
int g = 0;
void* run(void* pt) {
int id = (int) pt;
for (int i = 0; i < 10000; i++) {
lock.getLock(id);
//cout<<"Thread "<<id<<" get lock, g="<<g<<endl;
int next = g + 1;
g = next;
//cout<<"Thread "<<id<<" release lock, g="<<g<<endl;
lock.releaseLock(id);
}
return NULL;
}
int main() {
pthread_t th[Tcount];
long mtime, seconds, useconds;
struct timeval start, end;
gettimeofday(&start, NULL);
for (int i = 0; i < Tcount; i++) {
pthread_create(&th[i], NULL, run, (void*) (i+10));
}
for (int i = 0; i < Tcount; i++) {
pthread_join(th[i], 0);
}
gettimeofday(&end, NULL);
seconds = end.tv_sec - start.tv_sec;
useconds = end.tv_usec - start.tv_usec;
mtime = ((seconds) * 1000000 + useconds);
cout << "g=" << g << endl;
cout << "time=" << mtime << endl;
return 0;
}
You cannot implement a mutex by using the volatile keyword as the operations may not be atomic. This means that the OS might switch to a different thread before the operation has completed.
For mutex you have to use the OS. It is the only thing that knows when threads are being switched.