deadlock in using data for mutex and waits - c++

I need some help for a concurrent c++ programming.
I have a file of names, named "names.txt", in this format:
0 James
1 Sara
2 Isaac
And I have another file named "op.txt" that contains some operations on names file, in this format:
0 1 + // this means add Sara to James and store it in 0 position
1 2 $ // this means swap values in position 1 and position 2
and a file "output.txt" that has the output of operations, in this format:
0 JamesSara
1 Isaac
2 Sara
The problem says that create a thread for read names.txt and op.txt and store them. Next create some variable threads to do operations concurrently and at last do the output.txt in a thread.
Here is my code for this problem, and it works correctly when number of concurrent threads are
greater then 2. But the output for 1 and 2 thread are incorrect.
What I missed in this code?
#include <fstream>
#include <iostream>
#include <vector>
#include <sstream>
#include <cstdlib>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <deque>
using namespace std;
std::mutex _opMutex;
std::condition_variable _initCondition;
std::condition_variable _operationCondition;
int _counter = 0;
int _initCounter = 0;
int _doOperationCounter = 0;
struct OperationStruct
{
int firstOperand;
int secondOperand;
char cOperator;
};
const int THREADS = 5;
std::deque<std::pair<int, string> > _nameVector;
std::deque<OperationStruct> _opStructVec;
void initNamesAndOperations()
{
ifstream infile;
std::pair<int, string> namePair;
infile.open("names.txt");
if (!infile)
{
cout << "Unable to open file";
exit(-1);
}
int id;
string value;
while (infile >> id >> value)
{
namePair.first = id;
namePair.second = value;
_nameVector.push_back(namePair);
}
infile.close();
infile.open("op.txt");
if (!infile)
{
cout << "Unable to open file";
exit(-1);
}
int firstOperand;
int secondOperand;
char cOperator;
while (infile >> firstOperand >> secondOperand >> cOperator)
{
OperationStruct opSt;
opSt.firstOperand = firstOperand;
opSt.secondOperand = secondOperand;
opSt.cOperator = cOperator;
_opStructVec.push_back(opSt);
++_initCounter;
}
infile.close();
return;
}
void doOperationMath(int firstIndex, string firstValue, string secondValue, char cOp)
{
//basic mathematics
switch (cOp)
{
case '+':
{
for (int i = 0; i < _nameVector.size(); ++i)
{
std::pair<int, string> acc = _nameVector[i];
if (acc.first == firstIndex)
{
acc.second = firstValue + secondValue;
_nameVector[i].second = acc.second;
}
}
}
break;
default:
break;
}
++_doOperationCounter;
}
void doOperationSwap(int firstIndex, int secondIndex, string firstValue, string secondValue)
{
//swap
for (int i = 0; i < _nameVector.size(); ++i)
{
if (_nameVector[i].first == firstIndex)
_nameVector[i].second = secondValue;
if (_nameVector[i].first == secondIndex)
_nameVector[i].second = firstValue;
}
++_doOperationCounter;
}
void doOperations()
{
while (_doOperationCounter < _initCounter)
{
std::unique_lock<mutex> locker(_opMutex);
_initCondition.wait(locker, [](){return !_opStructVec.empty(); });
OperationStruct opSt = _opStructVec.front();
_opStructVec.pop_front();
locker.unlock();
_operationCondition.notify_one();
int firstId = opSt.firstOperand;
int secondId = opSt.secondOperand;
char cOp = opSt.cOperator;
string firstValue = "";
string secondValue = "";
for (int j = 0; j < _nameVector.size(); ++j)
{
std::pair<int, string> acc = _nameVector[j];
if (firstId == acc.first)
firstValue = acc.second;
if (secondId == acc.first)
secondValue = acc.second;
}
if (cOp == '$')
{
doOperationSwap(firstId, secondId, firstValue, secondValue);
}
else
{
doOperationMath(firstId, firstValue, secondValue, cOp);
}
}
return;
}
void doOutputFile()
{
ofstream outfile;
outfile.open("sampleOutput.txt", std::ios::out | std::ios::app);
if (!outfile)
{
cout << "Unable to open the file";
exit(-1);
}
while (_counter < _initCounter)
{
std::unique_lock<mutex> locker(_opMutex);
_operationCondition.wait(locker, [](){return !_nameVector.empty(); });
auto accPair = _nameVector.front();
_nameVector.pop_front();
locker.unlock();
outfile << accPair.first << " " << accPair.second << endl;
++_counter;
}
return;
}
int main()
{
thread th1(initNamesAndOperations);
std::vector<thread> operationalThreads;
for (int i = 0; i < THREADS; ++i)
{
operationalThreads.push_back(thread(doOperations));
}
thread th3(doOutputFile);
th1.join();
for (auto& opthread : operationalThreads)
opthread.join();
th3.join();
return 0;
}

If a variable is modified from multiple threads, you might have to use some synchronisation to ensure that the proper value is read. The simplest way would probably be to use std::atomic for your variables to ensure that operations are properly sequenced.
Also, there is nothing in your code to ensure that your doOperations thread won't finish before you have read the whole file.
Obviously, you need to either read the whole data first or have a way to wait for some data to become available (or to reach the end of data). If reading the initial data is fast but processing is slow, then the easier solution is to read the data before starting processing threads.
What is probably happening is that if you create a lot of threads, by the time you create the last thread, the initNamesAndOperations would have read the whole file.
I highly recommend you to buy and read C++ Concurrency in Action by Anthony Williams. By reading such book, you will get a good understanding on modern C++ multithreading and it will help you a lot to write correct code.

Related

writing to a vector< vector<bool> >

I'm writing for class an implementation of Conway's Game of Life in a toroid. The function cargarToroide (loadToroid) should load from a file into the vector the appropriate status (alive or dead - True or False - 1 or 0) of each cell (celda), it's signature is as follows:
toroide cargarToroide(string nombreArchivo, bool &status);
nombreArchivo is the name of the file, and status should be false if there is any problem loading the file or in the format of the file.
The data structure is defined like this (I cannot change it):
typedef vector< vector<bool> > toroide;
the file is structured like this:
numberOfLines numberOfColums
list of the values of the cells
number of live cells
For example:
4 4
1 0 0 0
0 0 1 0
0 0 0 1
0 1 0 0
4
The thing is, I cannot find a way to make it work. I've read online that vector<bool> has problems when you try to load it the usual way, which was the first thing I've tried.
toroide cargarToroide(string nombreArchivo, bool &status)
{
toroide t;
ifstream fi (nombreArchivo);
int cantidadFilas, cantidadColumnas;
int celda;
if(!fi){
status = false;
}
fi >> cantidadFilas;
fi >> cantidadColumnas;
for(int i = 0; i < cantidadFilas; i++) {
for (int j = 0; j < cantidadColumnas; j++) {
fi >> celda;
if(celda == 1) {
t[i].push_back(true);
}
else if(celda == 0){
t[i].push_back(false);
}
else{
status = false;
return t;
}
}
}
return t;
}
I've also tried defining celda as a boolean and just using
t[i].push_back(celda);
What would be the best way to approach this using C++11?
You need to resize the outer vector before you can use operator[] on it. You should also use the proper type (bool) when reading the data and check the input file for errors. I've commented in the code:
#include <iostream>
#include <fstream>
#include <vector>
typedef std::vector< std::vector<bool> > toroide;
// Both cargarToroide and cargarToroide_improved can be used
bool cargarToroide_improved(const std::string& nombreArchivo, toroide& in_toroide)
{
std::ifstream fi(nombreArchivo);
if(!fi) return false;
int cantidadFilas, cantidadColumnas, liveCells=0;
// use a bool to read the bool data
bool celda;
fi >> cantidadFilas;
fi >> cantidadColumnas;
// check if the stream is in a failed state
if(fi.fail()) return false;
// Temporary used to not mess with in_toroide until we're finished.
// Create it with cantidadFilas default inserted rows
toroide t(cantidadFilas);
for(auto& row : t) {
// default insert columns into the row
row.resize(cantidadColumnas);
for (int col = 0; col < cantidadColumnas; ++col) {
fi >> celda;
// check if the stream is in a failed state
// (non-bool read or the file reached eof())
if(fi.fail()) return false;
// set column value in the row
row[col] = celda;
// count live cells
liveCells += celda;
}
}
// compare live cells in matrix with checksum
int cmpLive;
fi >> cmpLive;
if(fi.fail() || cmpLive!=liveCells) return false;
// a successful toroide was read, swap your temporary
// toroide with the user supplied one
std::swap(t, in_toroide);
return true;
}
// if the signature of this function really can't be changed (which it should),
// make it a proxy for the function with a slightly nicer interface
// Like this:
toroide cargarToroide(std::string nombreArchivo, bool &status)
{
toroide rv;
status = cargarToroide_improved(nombreArchivo, rv);
return rv;
}
Using the improved signature:
int main(int argc, char* argv[]) {
std::vector<std::string> args(argv+1, argv+argc);
for(auto& file : args) {
toroide my_toroide;
if(cargarToroide_improved(file, my_toroide)) {
for(auto& r : my_toroide) {
for(auto c : r) {
std::cout << c << " ";
}
std::cout << "\n";
}
} else {
std::clog << "failed loading " << file << "\n";
}
}
}
Using the signature you're forced to use:
int main(int argc, char* argv[]) {
std::vector<std::string> args(argv+1, argv+argc);
for(auto& file : args) {
bool status;
toroide my_toroide = cargarToroide(file, status);
if(status) {
for(auto& r : my_toroide) {
for(auto c : r) {
std::cout << c << " ";
}
std::cout << "\n";
}
} else {
std::clog << "failed loading " << file << "\n";
}
}
}
If you know the number of rows at compile time (as you do in this case) you can use resize. In this case you will have to do the below before both the for loops.
t.resize(cantidadFilas);
In fact, you can do the same for the columns as well. Then you would no longer need to use push_back in the inner for loop as well.
If do not know the number of rows then you will just use push_back and add the rows to toroide. Then you would add the below lines before the second for loop.
vector<bool> row;
t.push_back(row)

How can I trace back the error

I was assigned to create an array check (to see if the array is increasing, decreasing, or neither [then exiting if neither]) and a recursive binary search for one of my assignments. I was able to do these things after some help from my peers, but I need help in finding what seems to be causing the error
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
Aborted
when running the code. I Googled this error and this error seems to be vague or I just am not understanding. It compiles without errors, but I need help in what finding what I did wrong. It is able to run without the binarySearchR function and its associating code, as the array check on its own was the previous assignment. Below is the code, and I thank you so much in advance!
#include <iosteam>
#include <string>
#include <cstdlib>
#include <fstream>
using namespace std;
int checkArraySort (string *fileLines, int numberOfLines);
int binarySearchR (string *fileLines, string searchKey, int iMin, int iMax);
int main ()
{
int numberOfLines = 0;
string searchKey = 0;
cout << "Input search key: ";
cin >> searchKey;
ifstream fileIn;
fileIn.open("words_in.txt");
string line;
if (fileIn.eof()) /* Checks file to see if it is blank before proceeding */
{
exit (EXIT_SUCCESS);
}
else
{
while(!(fileIn.eof()))
{
fileIn >> line;
numberOfLines++;
}
fileIn.close(); /* closes fileIn, need to reopen to reset the line location */
fileIn.open("words_in.txt");
string *fileInLines;
fileInLines = new string[numberOfLines];
for (int i = 0; i < numberOfLines; i++)
{
fileIn >> line;
fileInLines[i] = line;
}
fileIn.close(); /* closes fileIn */
int resultingCheck = checkArraySort(fileInLines, numberOfLines);
if (resultingCheck == -1)
{
cout << "The array is sorted in descending order." << endl;
}
else if (resultingCheck == 1)
{
cout << "The array is sorted in ascending order." << endl;
}
else
{
cerr << "ERROR: Array not sorted!" << endl;
exit (EXIT_FAILURE);
}
int searchResult = binarySearchR (fileInLines, searchKey, 0, numberOfLines);
if (!searchResult == -1)
{
cout << "Key found at index " << searchResult << "." << endl;
}
else
{
cout << "Key not found at any index." << endl;
}
exit (EXIT_SUCCESS);
}
}
int checkArraySort (string *fileLines, int numberOfLines)
{
int result = 1; /* Ascending by default */
for (int i = 1; i < numberOfLines; i++) /* Checks if decending */
{
if (fileLines[i] < fileLines[i-1])
{
result = -1;
}
}
if (result == -1) /* Makes sure it is descending (or if it is neither) */
{
for (int i = 1; i < numberOfLines; i++)
{
if (fileLines[i] > fileLines[i-1])
{
result = 0;
}
}
}
return result;
}
int binarySearchR (string *fileLines, string searchKey, int iMin, int iMax)
{
// so, its gotta look at the center value and each times, it discards half of the remaining list.
if (iMax < iMin) /* If the minimum is greater than the maximum */
{
return -1;
}
else
{
int iMid = (iMin + iMax) / 2;
if (fileLines[iMid] > searchKey) /* If the key is in the lower subset */
{
return binarySearchR (fileLines, searchKey, iMin, iMid - 1);
}
else if (fileLines[iMid] < searchKey) /*If the key is in the upper subset */
{
return binarySearchR (fileLines, searchKey, iMin, iMid + 1);
}
else /*If anything else besides the two */
{
return iMid;
}
}
}
The easy way: add a bunch of cout s to see where you program goes and what the values are.
Pros
Easy to do
Cons
Requires a recompile each time you want to add more info
The hard way: Learn to use a debugger
Pros
Can inspect "on the fly"
Don't need to rebuild
Can use what you learn in every other C++ program
Cons
Requires a bit of research to learn how to do it.

How to correctly create a ofstream of a struct vector? (abort() is called)

I'm currently building a game and in this part my goal is to update a previously created file of High Scores ("rank.txt")
To do so, I've created a couple of functions so they can read what's in the txt file and so it can update it. Though the reading is ok, while adding the update function it gives me an error and tells me abort() has been called.
void highScoresUpdate(vector<PlayerInfo> player)
{
// This function is responsible for updating the High Scores Table present in the RANK.TXT file
vector<HighScoresStruct> highScores_temp;
HighScoresStruct temp;
ofstream out_file_3("rank.txt");
highScores_temp = readHighScores("rank.txt");
for (int k = 0; k < player.size(); k++)
{
player[k].name = temp.name;
player[k].score = temp.score;
player[k].time = temp.time;
highScores_temp.push_back(temp);
}
sort(highScores_temp.begin(), highScores_temp.end(), compareByScore);
for (int i = 0; i < highScores_temp.size(); i++)
{
out_file_3 << highScores_temp[i].name << endl << highScores_temp[i].score << endl << highScores_temp[i].time << endl;
}
out_file_3.close();
}
For background information, so i don't spam this thread with code all you need to know is that readHighScores is the function responsible for extracting information from the txt file and placing it on a vector .
The structs and comparebyScore funtion are listed below.
bool compareByScore(const HighScoresStruct &a, const HighScoresStruct &b)
{
if (a.score < b.score)
return true;
else if (a.score == b.score)
{
if (a.time > b.time)
return true;
else
return false;
}
else
return false;
}
struct PlayerInfo
{
string name;
unsigned int score;
vector<char> hand;
bool inGame;
unsigned int time;
};
struct HighScoresStruct
{
string name;
unsigned int score;
unsigned int time;
};
So... Can anyone help me?

Segmentation fault multithreading C++ 11

Introduction
I have a vector entities containing 44 million names. I want to split it into 4 parts and process each part in parallel. Class Freebase contains the function loadData() which is used to split the vector and call function multiThread in order to do the processing.
loadEntities() reads a text file containing the names. I didn't put the implementation in the class because it's not important
loadData() splits the vector entities that was initialized in the constructor into 4 parts and adds every part the vector<thread> threads as follows:
threads.push_back(thread(&Freebase::multiThread, this, i, i + right, ref(data)));
multiThread is the function where I process the files
i and i+right are the indices used in the for loop of multithread to loop through entities
returnValues is a subfunction of multiThreadand is used to call an external function.
Problem
cout <<"Entity " << entities[i] << endl; is showing the following results:
Entity m.0rzf6wv (ok)
Entity m.0rzf70 (ok)
Entity m.068s4h9 m.0n_k8bz (WRONG)
Entity Entity m.068s5_1 (WRONG)
The last 2 outputs are wrong. The output should be:
Entity name not entity entity name nor entity name name
This is causing a segmentation fault when the input is being sent to function returnValues. How can I solve it?
Source Code
#ifndef FREEBASE_H
#define FREEBASE_H
class Freebase
{
public:
Freebase(const std::string &, const std::string &, const std::string &, const std::string &);
void loadData();
private:
std::string _serverURL;
std::string _entities;
std::string _xmlFile;
void multiThread(int,int, std::vector<std::pair<std::string, std::string>> &);
//private data members
std::vector<std::string> entities;
};
#endif
#include "Freebase.h"
#include "queries/SparqlQuery.h"
Freebase::Freebase(const string & url, const string & e, const string & xmlFile, const string & tfidfDatabase):_serverURL(url), _entities(e), _xmlFile(xmlFile), _tfidfDatabase(tfidfDatabase)
{
entities = loadEntities();
}
void Freebase::multiThread(int start, int end, vector<pair<string,string>> & data)
{
string basekb = "PREFIX basekb:<http://rdf.basekb.com/ns/> ";
for(int i = start; i < end; i++)
{
cout <<"Entity " << entities[i] << endl;
vector<pair<string, string>> description = returnValues(basekb + "select ?description where {"+ entities[i] +" basekb:common.topic.description ?description. FILTER (lang(?description) = 'en') }");
string desc = "";
for(auto &d: description)
{
desc += d.first + " ";
}
data.push_back(make_pair(entities[i], desc));
}
}
void Freebase::loadData()
{
vector<pair<string, string>> data;
vector<thread> threads;
int Size = entities.size();
//split database into 4 parts
int p = 4;
int right = round((double)Size / (double)p);
int left = Size % p;
float totalduration = 0;
vector<pair<int, int>> coordinates;
int counter = 0;
for(int i = 0; i < Size; i += right)
{
if(i < Size - right)
{
threads.push_back(thread(&Freebase::multiThread, this, i, i + right, ref(data)));
}
else
{
threads.push_back(thread(&Freebase::multiThread, this, i, Size, ref(data)));
}
}//end outer for
for(auto &t : threads)
{
t.join();
}
}
vector<pair<string, string>> Freebase::returnValues(const string & query)
{
vector<pair<string, string>> data;
SparqlQuery sparql(query, _serverURL);
string result = sparql.retrieveInformations();
istringstream str(result);
string line;
//skip first line
getline(str,line);
while(getline(str, line))
{
vector<string> values;
line.erase(remove( line.begin(), line.end(), '\"' ), line.end());
boost::split(values, line, boost::is_any_of("\t"));
if(values.size() == 2)
{
pair<string,string> fact = make_pair(values[0], values[1]);
data.push_back(fact);
}
else
{
data.push_back(make_pair(line, ""));
}
}
return data;
}//end function
EDIT:
Arnon Zilca is correct in his comments. You are writing to a single vector from multiple threads (in Freebase::multiThread()), a recipe for disaster. You can use a mutex as described below to protect the push_back operation.
For more info on thread safety on containers see Is std::vector or boost::vector thread safe?.
So:
mtx.lock();
data.push_back(make_pair(entities[i], desc));
mtx.unlock();
Another option is using the same strategy as you do in returnValues, creating a local vector in multiThread and only pushing the contents to the data vector when thread is done processing.
So:
void Freebase::multiThread(int start, int end, vector<pair<string,string>> & data)
{
vector<pair<string,string>> threadResults;
string basekb = "PREFIX basekb:<http://rdf.basekb.com/ns/> ";
for(int i = start; i < end; i++)
{
cout <<"Entity " << entities[i] << endl;
vector<pair<string, string>> description = returnValues(basekb + "select ?description where {"+ entities[i] +" basekb:common.topic.description ?description. FILTER (lang(?description) = 'en') }");
string desc = "";
for(auto &d: description)
{
desc += d.first + " ";
}
threadResults.push_back(make_pair(entities[i], desc));
}
mtx.lock()
data.insert(data.end(), threadResults.begin(), threadResults.end());
mtx.unlock()
}
Note: I would suggest using a different mutex than the one you use for the cout. The overall result vector data is a different resource than cout. So threads who want to use cout, should not have to wait for another thread to finish with data.
/EDIT
You could use a mutex around
cout <<"Entity " << entities[i] << endl;
That would prevent multiple threads using cout at "the same time". That way you can be sure that an entire message is printed by a thread before another thread gets to print a message. Note that this will impact your performance since threads will have to wait for the mutex to become available before they are allowed to print.
Note: Protecting the cout will only cleanup your output on the stream, it will not influence the behavior of the rest of the code, see above for that.
See http://www.cplusplus.com/reference/mutex/mutex/lock/ for an example.
// mutex::lock/unlock
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex
std::mutex mtx; // mutex for critical section
void print_thread_id (int id) {
// critical section (exclusive access to std::cout signaled by locking mtx):
mtx.lock();
std::cout << "thread #" << id << '\n';
mtx.unlock();
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(print_thread_id,i+1);
for (auto& th : threads) th.join();
return 0;
}

Making threads redo a print function in order

This is a home assignment.
Have to print a string(given as input) in small chunks(Size given as input) by multiple threads one at a time in order 1,2,3,1,2,3,1,2(number of threads is given as input).
A thread does this printing function on creation and I want it to redo it after all the other threads. I face two problems:
1. Threads don't print in fixed order(mine gave 1,3,2,4 see output)
2. Threads need to re print till the entire string is exhausted.
This is what I tried...
#include<iostream>
#include<mutex>
#include<thread>
#include<string>
#include<vector>
#include<condition_variable>
#include<chrono>
using namespace std;
class circularPrint{
public:
int pos;
string message;
int nCharsPerPrint;
mutex mu;
condition_variable cv;
circularPrint(){
pos=0;
}
void shared_print(int threadID){
unique_lock<mutex> locker(mu);
if(pos+nCharsPerPrint<message.size())
cout<<"Thread"<<threadID<<" : "<<message.substr(pos,nCharsPerPrint)<<endl;
else if(pos<message.size())
cout<<"Thread"<<threadID<<" : "<<message.substr(pos)<<endl;
pos+=nCharsPerPrint;
}
};
void f(circularPrint &obj,int threadID){
obj.shared_print(threadID);
}
int main(){
circularPrint obj;
cout<<"\nMessage : ";
cin>>obj.message;
cout<<"\nChars : ";
cin>>obj.nCharsPerPrint;
int nthreads;
cout<<"\nThreads : ";
cin>>nthreads;
vector<thread> threads;
for(int count=1;count<=nthreads;++count)
{
threads.push_back(thread(f,ref(obj),count));
}
for(int count=0;count<nthreads;++count)
{
if(threads[count].joinable())
threads[count].join();
}
return 0;
}
Why would you want to multithread a method that can only be executed once at a time?
Anyway, something like this below? Be aware that the take and print use different locks and that there is a chance the output does not show in the expected order (hence, the why question above).
#include <iostream>
#include <mutex>
#include <thread>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class circularPrint
{
public:
int pos;
string message;
int nCharsPerPrint;
mutex takeLock;
mutex printLock;
circularPrint() {
pos = 0;
}
string take(int count) {
lock_guard<mutex> locker(takeLock);
count = std::min(count, (int)message.size() - pos);
string substring = message.substr(pos, count);
pos += count;
return substring;
}
void print(int threadID, string& message) {
lock_guard<mutex> locker(printLock);
cout << "Thread" << threadID << " : " << message << endl;
}
void loop(int threadID) {
string message;
while((message = take(nCharsPerPrint)).size() > 0) {
print(threadID, message);
}
}
};
void f(circularPrint &obj, int threadID)
{
obj.loop(threadID);
}
int main()
{
circularPrint obj;
//cout << "\nMessage : ";
//cin >> obj.message;
//cout << "\nChars : ";
//cin >> obj.nCharsPerPrint;
int nthreads;
//cout << "\nThreads : ";
//cin >> nthreads;
nthreads = 4;
obj.message = "123456789012345";
obj.nCharsPerPrint = 2;
vector<thread> threads;
for (int count = 1; count <= nthreads; ++count)
threads.push_back(thread(f, ref(obj), count));
for (int count = 0; count < nthreads; ++count) {
if (threads[count].joinable())
threads[count].join();
}
return 0;
}
Currently each thread exits after printing one message - but you need more messages than threads, so each thread will need to do more than one message.
How about putting an infinite loop around your current locked section, and breaking out when there are no characters left to print?
(You may then find that the first thread does all the work; you can hack that by putting a zero-length sleep outside the locked section, or by making all the threads wait for some single signal to start, or just live with it.)
EDIT: Hadn't properly realised that you wanted to assign work to specific threads (which is normally a really bad idea). But if each thread knows its ID, and how many there are, it can figure out which characters it is supposed to print. Then all it has to do is wait till all the preceding characters have been printed (which it can tell using pos), do its work, then repeat until it has no work left to do and exit.
The only tricky bit is waiting for the preceding work to finish. You can do that with a busy wait (bad), a busy wait with a sleep in it (also bad), or a condition variable (better).
You need inter thread synchronization, each thread doing a loop "print, send a message to next one, wait for a message (from the last thread)".
You can use semaphores, events, messages or something similar.
Something as:
#include <string>
#include <iostream>
#include <condition_variable>
#include <thread>
#include <unistd.h>
using namespace std;
// Parameters passed to a thread.
struct ThreadParameters {
string message; // to print.
volatile bool *exit; // set when the thread should exit.
condition_variable* input; // condition to wait before printing.
condition_variable* output; // condition to set after printing.
};
class CircularPrint {
public:
CircularPrint(int nb_threads) {
nb_threads_ = nb_threads;
condition_variables_ = new condition_variable[nb_threads];
thread_parameters_ = new ThreadParameters[nb_threads];
threads_ = new thread*[nb_threads];
exit_ = false;
for (int i = 0; i < nb_threads; ++i) {
thread_parameters_[i].message = to_string(i + 1);
thread_parameters_[i].exit = &exit_;
// Wait 'your' condition
thread_parameters_[i].input = &condition_variables_[i];
// Then set next one (of first one if you are the last).
thread_parameters_[i].output =
&condition_variables_[(i + 1) % nb_threads];
threads_[i] = new thread(Thread, &thread_parameters_[i]);
}
// Start the dance, free the first thread.
condition_variables_[0].notify_all();
}
~CircularPrint() {
// Ask threads to exit.
exit_ = true;
// Wait for all threads to end.
for (int i = 0; i < nb_threads_; ++i) {
threads_[i]->join();
delete threads_[i];
}
delete[] condition_variables_;
delete[] thread_parameters_;
delete[] threads_;
}
static void Thread(ThreadParameters* params) {
for (;;) {
if (*params->exit) {
return;
}
{
// Wait the mutex. We don't really care, by condition variables
// need a mutex.
// Though the mutex will be useful for the real assignement.
unique_lock<mutex> lock(mutex_);
// Wait for the input condition variable (frees the mutex before waiting).
params->input->wait(lock);
}
cout << params->message << endl;
// Free next thread.
params->output->notify_all();
}
}
private:
int nb_threads_;
condition_variable* condition_variables_;
ThreadParameters* thread_parameters_;
thread** threads_;
bool exit_;
static mutex mutex_;
};
mutex CircularPrint::mutex_;
int main() {
CircularPrint printer(10);
sleep(3);
return 0;
}
using vector<shared_ptr<...>> would be more elegant than just arrays, though this works:
g++ -std=c++11 -o test test.cc -pthread -Wl,--no-as-needed
./test