I have a quite complicated problem with my logger class. Its is a singleton pattern logging class. A thread is created only for taking out items from queue and logging them. Generally everything works fine and the error occurs very occasionally as segmentation fault. It was happening even more often before I decided to put a mutex on whole method chain. With that mutex i dont understand why is the segmentation fault occuring. The class got quite complicated because of the operator<< usage. Problem with it is that the operator template is run as many times, as many items are passed using <<. Because of that other threads can cut in between those templete calls.
The general usage looks like this:
1. instance method is called (creating or pointing to the instance pointer (singleton). mutex is locked at this moment.
2. any called methods are called, for example operator<< template.
3. finishing method is called, placing log in the queue and unlocking mutex.
I have edited the code and tried to take the fata gathering out of the singleton class to the proxy class.
main.c:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "CLogger.h"
#include "CTestClass.h"
using namespace std;
int main()
{
CLogger::instance().log.startLog("/home/lukasz/Pulpit/test.txt", true);
CLogger::instance().log.setLogLevel(CLogger::ElogLevel::eDebug);
CLogger::instance().printf("Print test");
CLogger::instance().printf("Print test");
CLogger::instance().printf("Print test");
CLogger::instance() << "Stream test" ;
CLogger::instance() << "Stream test" ;
CLogger::instance() << "Stream test" ;
//CTestClass test1(1);
//CTestClass test2(2);
//CTestClass test3(3);
sleep(3);
CLogger::instance().log.stopLog();
return 0;
}
CLogger.h:
#ifndef CLOGGER_H_
#define CLOGGER_H_
#include <iostream>
#include <deque>
#include <string>
#include <mutex>
#include <condition_variable>
#include <pthread.h>
#include <ostream>
#include <fstream>
#include <sstream>
#include <ctime>
#include <iomanip>
#include <sys/time.h>
#include <stdarg.h>
#include <assert.h>
#include "CTeebuf.h"
using namespace std;
class CLoggerProxy;
/*!
* \brief Singleton class used for logging
*/
class CLogger
{
public:
/*!
* \brief Describes the log level of called \ref CLogger object.
*/
enum class ElogLevel { eNone = 0, eError, eWarning, eInfo, eDebug };
/*!
* Structure describing a single log item:
*/
struct logline_t
{
string logString; /*!< String line to be saved to a file (and printed to cout). */
ElogLevel logLevel; /*!< The \ref ElogLevel of this line. */
timeval currentTime; /*!< time stamp of current log line */
};
static CLogger* internalInstance(ElogLevel lLevel = ElogLevel::eDebug);
static CLoggerProxy instance(ElogLevel lLevel = ElogLevel::eDebug);
bool startLog(string fileName, bool verbose);
void setLogLevel(ElogLevel ll);
void stopLog();
void finaliseLine(logline_t* log);
protected:
virtual void threadLoop();
private:
CLogger() {}; // Private so that it can not be called
CLogger(CLogger const&) {}; // copy constructor is private
CLogger& operator= (CLogger const&) {}; // assignment operator is private
/*!< Global static pointer used to ensure a single instance of the class */
static CLogger* mp_instance;
bool m_logStarted;
ElogLevel m_userDefinedLogLevel;
ofstream m_logFileStream;
bool m_verbose;
bool m_finishLog;
timeval m_initialTime;
static void * threadHelper(void* handler)
{
((CLogger*)handler)->threadLoop();
return NULL;
}
deque<logline_t*> m_data;
mutex m_mutex2;
condition_variable m_cv;
pthread_t m_thread;
logline_t pop_front();
void push_back(logline_t* s);
};
/*!
* RAII class used for its destructor, to add a log item to the queue
*/
class CLoggerProxy
{
public:
CLogger &log;
CLoggerProxy(CLogger &logger) : log(logger)
{
mp_logLine = new CLogger::logline_t;
gettimeofday(&mp_logLine->currentTime, NULL);
}
~CLoggerProxy() { log.finaliseLine(mp_logLine); }
void printf(const char* text, ...);
/*!
* Takes the data from the stream and adds it to the current string.
* #param t stream item
* #return \ref object address
*/
template <typename T>
CLoggerProxy& operator<< (const T &t)
{
ostringstream stream;
stream << t;
mp_logLine->logString = (stream.str() + " ");
return *this;
}
private:
CLogger::logline_t* mp_logLine;
};
#endif /* CLOGGER_H_ */
CLogger.cpp:
#include "CLogger.h"
using namespace std;
CLogger* CLogger::mp_instance = NULL;
/*!
* This function is called to create an instance of the class.
* Calling the constructor publicly is not allowed. The constructor
* is private and is only called by this Instance function.
* #param lLevel Log level for current object
*/
CLogger* CLogger::internalInstance(ElogLevel lLevel)
{
// Only allow one instance of class to be generated.
if (!mp_instance)
{
mp_instance = new CLogger;
assert(mp_instance);
}
return mp_instance;
}
/*!
* This method is called in order to use the methods
* within the objects.
* #param lLevel Log level for current object
*/
CLoggerProxy CLogger::instance(ElogLevel lLevel)
{
return CLoggerProxy(*internalInstance(lLevel));
}
/*!
* \brief Starts the logging system.
*
* This method creates and opens the log file,
* then opens it and creates the threadloop for messages deque.
* #param fileName desired log file path,
* #param verbose when set true, logging will also be printed to standard output.
*/
bool CLogger::startLog(string fileName, bool verbose)
{
if(remove(fileName.c_str()) != 0)
perror( "Error deleting file" );
m_logFileStream.open(fileName.c_str(), ios::out | ios::app);
if (!m_logFileStream.is_open())
{
cout << "Could not open log file " << fileName << endl;
return false;
}
m_finishLog = false;
m_verbose = verbose;
m_logStarted = true;
gettimeofday(&m_initialTime, NULL);
return (pthread_create(&(m_thread), NULL, threadHelper, this) == 0);
}
/*!
* \brief puts a \ref logline_t object at the end of the queue
* #param s object to be added to queue
*/
void CLogger::push_back(logline_t* s)
{
unique_lock<mutex> ul(m_mutex2);
m_data.emplace_back(move(s));
m_cv.notify_all();
}
/*!
* \brief takes a \ref logline_t object from the beggining of the queue
* #return first \ref logline_t object
*/
CLogger::logline_t CLogger::pop_front()
{
unique_lock<mutex> ul(m_mutex2);
m_cv.wait(ul, [this]() { return !m_data.empty(); });
logline_t retVal = move(*m_data.front());
assert(m_data.front());
delete m_data.front();
m_data.front() = NULL;
m_data.pop_front();
return retVal;
}
/*!
* \brief Sets the log level for the whole \ref CLogger object.
* If \ref m_logLine is equal or higher than set level, log
* is going to be printed.
* #param lLevel desired user define log level.
*/
void CLogger::setLogLevel(ElogLevel lLevel)
{
m_userDefinedLogLevel = lLevel;
}
/*!
* \brief Stops the logging system.
* Last final logline is being added and then the logging thread
* is being closed.
*/
void CLogger::stopLog()
{
m_finishLog = true;
//instance(ElogLevel::eNone).log << "CLogger Stop";
//pthread_join(m_thread, NULL);
}
/*!
* This function should be run in the \ref CLoggerProxy destructor.
* is pushes the gathered stream to the queue.
*/
void CLogger::finaliseLine(logline_t* log)
{
if (log->logString.size() > 0)
push_back(log);
else
delete log;
}
/*!
* \brief Adds text log to the string in the printf c way.
* Works faster than operator<< and its more atomic.
* #param text pointer to a character string.
* #param ... argptr parameters
*/
void CLoggerProxy::printf(const char* text, ...)
{
va_list argptr;
va_start(argptr, text);
char* output = NULL;
vasprintf(&output, text, argptr);
mp_logLine->logString = output;
va_end(argptr);
}
/*!
* The loop running in a separate thread. It take items of the
* log deque object (if there are any) and saves them to a file.
*/
void CLogger::threadLoop()
{
logline_t logline;
const string logLevelsStrings[] = {"eNone", "eError", "eWarning", "eInfo", "eDebug" };
COteestream tee;
tee.add(m_logFileStream);
if (m_verbose)
tee.add(cout);
struct sched_param param;
param.__sched_priority = 0;
if(!sched_setscheduler(0, SCHED_IDLE, ¶m))
instance().printf("Clogger scheduler policy set to %d", sched_getscheduler(0));
int secs = 0;
int h = 0;
int m = 0;
int s = 0;
do
{
logline = pop_front(); // waits here for new lines
secs = logline.currentTime.tv_sec - m_initialTime.tv_sec;
h = secs / 3600;
m = ( secs % 3600 ) / 60;
s = ( secs % 3600 ) % 60;
tee << "["
<< setw(2) << setfill('0') << h
<< ":"
<< setw(2) << setfill('0') << m
<< ":"
<< setw(2) << setfill('0') << s
<< "."
<< setw(6) << setfill('0') << logline.currentTime.tv_usec
<< "]"
<< "["
<< setw(2) << setfill('0') << m_data.size()
<< "]"
<< "["
<< logLevelsStrings[(int)logline.logLevel]
<< "] "
<< logline.logString << "\n" << flush;
}
//while(!(m_finishLog && m_data.empty()));
while(1);
m_logFileStream.close();
}
There are several problems with your code.
Singleton
// Only allow one instance of class to be generated.
if (!mp_instance)
{
mp_instance = new CLogger;
assert(mp_instance);
}
This is a classic problem. It may be called by different threads at the same time, and it is not thread safe. You may end up with several instances of your singleton.
The queue (m_data)
Clients of your logger put their messages into this queue (apparently secured by m_mutext).
m_data.emplace_back(move(s));
m_cv.notify_all();
Your logger thread removes the messages in its own thread (secured by m_mutex2).
unique_lock<mutex> ul(m_mutex2);
m_cv.wait(ul, [this]() { return !m_data.empty(); });
logline_t retVal = move(*m_data.front());
assert(m_data.front());
delete m_data.front();
m_data.front() = NULL;
m_data.pop_front();
return retVal;
The problem here is, you use 2 different mutexes to synchronize access to the same object. This cannot work.
In addition you access m_data in your thread without any locking at all:
<< setw(2) << setfill('0') << m_data.size()
or
while(!(m_finishLog && m_data.empty()));
The log message (mp_logLine)
You try to lock too much data. The pointer to your log message is meant to be used by a single thread at a time. But you store it in the main logger class which is accessed by all threads. You already have a proxy for your logger which is private to the thread using it. Store your message there until it's finished, and then add it to the queue.
Generally said, minimize the amount of data to be locked. If you rework your code and the only object needing locking is your queue, you are on the right way.
This doesn't look threadsafe at all.
mp_logLine->logString += (stream.str() + " ");
This looks like it's shared between all threads that log to an instance. It's not clear from your code that += is thread safe.
Another point is when you push back an item into your queue you don't lock the mutex. If two threads are doing it at the same time they can mess up the deque. There's no guarantee that you can do push_back and pop_front in parallel, so put a mutex around it.
When you get an internal instance you lock mp_instance->m_mutex but it doesn't look like you're unlocking it.
You use bool m_logStarted from parallel threads and that can also introduce race conditions and inconsistencies.
Any of the above can cause your segmentation fault.
Getting multi-threading right is really hard. Debugging it is even harder. Try to offload the multi-threading component(s) to a library you know already works and add things in a single threaded context. In this case it would mean to use a separate class instance for every call to the log and then push the item to a producer-consumer queue that's implemented by some library and guaranteed thread-safe. There are many other ways to do it as well.
Related
I know the title is a bit broad so let me elaborate.
I have 2 processes running, one is writing into the shared memory, the other is reading from it.
To achieve shared memory effect I am using boost::interprocess (btw let me know if there are more convenient libraries).
So I implemented the following:
//Writer
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/windows_shared_memory.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
namespace ip = boost::interprocess;
class SharedMemory
{
public:
template<typename OpenOrCreate>
SharedMemory(OpenOrCreate criteria, const char* name, ip::mode_t mode, size_t size) :
name_(name),
sm_(std::make_shared<ip::windows_shared_memory>(criteria, name, mode, size))
{
}
template<typename OpenOrCreate>
SharedMemory(OpenOrCreate criteria, const char* name, ip::mode_t mode) :
name_(name),
sm_(std::make_shared<ip::windows_shared_memory>(criteria, name, mode))
{
}
std::shared_ptr<ip::windows_shared_memory> getSM()
{
return sm_;
}
private:
std::function<void()> destroyer_;
std::string name_;
std::shared_ptr<ip::windows_shared_memory> sm_;
};
int main()
{
SharedMemory creator(ip::create_only, "SharedMemory", ip::read_write, 10);
ip::mapped_region region(*creator.getSM(), ip::read_write);
std::memset(region.get_address(), 1, region.get_size());
int status = system("reader.exe");
std::cout << status << std::endl;
}
So I am creating shared memory, writing 1 to it then calling the reader exe. (I skip the reader part as its pretty much the same but instead of write it reads)
This code works fine, I write into memory and the other process reads it and prints my 1's.
But what if I have this 2 exes running at the same time and I want to write into memory then notify the other process that there is an update? How to signal from one exe/process to another?
The scenario is that I am streaming some live data, writing into memory and then telling the other process that there is an update.
I think there are more convenient approaches indeed.
In principle to synchronize between processes you use all the same approaches as synchronizing inside a process (between threads): using synchronization primitives (mutex/critical section, condition variable, semaphores, barriers etc.).
In addition, you need to have a data structure that you synchronize. This is precisely the Achilles' heel at the moment. There is a total absence of data structure here.
Though you can do raw byte access with your own logic, I don't see the appeal of using a high-level library in doing so. Instead I'd use a managed memory segment, that lets you find or construct typed objects by name. This may include your synchronization primitives.
In fact, you can expedite the process by using a message_queue which has all the synchronization already built-in.
Manual Sync: Writer using Segment Manager
I'll provide portable code because I donot have a windows machine. First let's think of a datastructure. A simple example would be a queue of messages. Let's use a deque<string>.
Not exactly trivial data structures, but the great news is that Boost Interprocess comes with all the nuts and bolts to make things work (using interprocess allocators).
namespace Shared {
using Segment = ip::managed_shared_memory;
using Mgr = Segment::segment_manager;
template <typename T>
using Alloc = bc::scoped_allocator_adaptor<ip::allocator<T, Mgr>>;
template <typename T> using Deque = bc::deque<T, Alloc<T>>;
using String = bc::basic_string<char, std::char_traits<char>, Alloc<char>>;
using DataStructure = Deque<String>;
class Memory {
public:
Memory(const char* name, size_t size)
: name_(name)
, sm_(ip::open_or_create, name, size)
, data_(*sm_.find_or_construct<DataStructure>("data")(
sm_.get_segment_manager()))
{
}
DataStructure& get() { return data_; }
DataStructure const& get() const { return data_; }
private:
std::string name_;
Segment sm_;
DataStructure& data_;
};
} // namespace Shared
There, now we can have the writer be something like:
int main()
{
Shared::Memory creator("SharedMemory", 10*1024*1024);
creator.get().emplace_back("Hello");
creator.get().emplace_back("World");
std::cout << "Total queued: " << creator.get().size() << "\n";
}
Which will print e.g.
Total queued: 2
Total queued: 4
Total queued: 6
Depending on the number of times you ran it.
The Reader side
Now lets do the reader side. In fact it's so much the same, let's put it in the same main program:
int main(int argc, char**)
{
Shared::Memory mem("SharedMemory", 10*1024*1024);
auto& data = mem.get();
bool is_reader = argc > 1;
if (not is_reader) {
data.emplace_back("Hello");
data.emplace_back("World");
std::cout << "Total queued: " << data.size() << "\n";
} else {
std::cout << "Found entries: " << data.size() << "\n";
while (!data.empty()) {
std::cout << "Dequeued " << data.front() << "\n";
data.pop_front();
}
}
}
Simple for a start. Now running e.g. test.exe READER will conversely print something like:
Locking & Synchronization
The goal is to run writer and reader concurrently. That's not safe as it is now, because of a lack of locking and synchronization. Let's add it:
class Memory {
static constexpr size_t max_capacity = 100;
public:
Memory(const char* name, size_t size)
: name_(name)
, sm_(ip::open_or_create, name, size)
, mx_(*sm_.find_or_construct<Mutex>("mutex")())
, cv_(*sm_.find_or_construct<Cond>("condition")())
, data_(*sm_.find_or_construct<DataStructure>("data")(
sm_.get_segment_manager()))
{ }
// ...
private:
std::string name_;
Segment sm_;
Mutex& mx_;
Cond& cv_;
DataStructure& data_;
};
Now let's be careful. Because we want all operations on the data_ queue to be synchronized, we shall not expose it as we did before (with the get() member function). Instead we expose the exact interface of operations we support:
size_t queue_length() const;
void enqueue(std::string message); // blocking when queue at max_capacity
std::string dequeue(); // blocking dequeue
std::optional<std::string> try_dequeue(); // non-blocking dequeue
These all do the locking as required, simply as you'd expect:
size_t queue_length() const {
ip::scoped_lock<Mutex> lk(mx_);
return data_.size();
}
It gets more interesting on the potentially blocking operations. I chose to have a maximum capacity, so enqueue needs to wait for capacity:
// blocking when queue at max_capacity
void enqueue(std::string message) {
ip::scoped_lock<Mutex> lk(mx_);
cv_.wait(lk, [this] { return data_.size() < max_capacity; });
data_.emplace_back(std::move(message));
cv_.notify_one();
}
Conversely, dequeue needs to wait for a message to become available:
// blocking dequeue
std::string dequeue() {
ip::scoped_lock<Mutex> lk(mx_);
cv_.wait(lk, [this] { return not data_.empty(); });
return do_pop();
}
Alternatively, you could make it non-blocking, just optionally returning a value:
// non-blocking dequeue
std::optional<std::string> try_dequeue() {
ip::scoped_lock<Mutex> lk(mx_);
if (data_.empty())
return std::nullopt;
return do_pop();
}
Now in main let's have three versions: writer, reader and continuous reader (where the latter demonstrates the blocking interface):
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_condition_any.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/interprocess/containers/deque.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <iostream>
#include <iomanip>
#include <optional>
namespace ip = boost::interprocess;
namespace bc = boost::container;
namespace Shared {
using Segment = ip::managed_shared_memory;
using Mgr = Segment::segment_manager;
template <typename T>
using Alloc = bc::scoped_allocator_adaptor<ip::allocator<T, Mgr>>;
template <typename T> using Deque = ip::deque<T, Alloc<T>>;
using String = ip::basic_string<char, std::char_traits<char>, Alloc<char>>;
using DataStructure = Deque<String>;
using Mutex = ip::interprocess_mutex;
using Cond = ip::interprocess_condition;
class Memory {
static constexpr size_t max_capacity = 100;
public:
Memory(const char* name, size_t size)
: name_(name)
, sm_(ip::open_or_create, name, size)
, mx_(*sm_.find_or_construct<Mutex>("mutex")())
, cv_(*sm_.find_or_construct<Cond>("condition")())
, data_(*sm_.find_or_construct<DataStructure>("data")(
sm_.get_segment_manager()))
{ }
size_t queue_length() const {
ip::scoped_lock<Mutex> lk(mx_);
return data_.size(); // caution: racy by design!
}
// blocking when queue at max_capacity
void enqueue(std::string message) {
ip::scoped_lock<Mutex> lk(mx_);
cv_.wait(lk, [this] { return data_.size() < max_capacity; });
data_.emplace_back(std::move(message));
cv_.notify_one();
}
// blocking dequeue
std::string dequeue() {
ip::scoped_lock<Mutex> lk(mx_);
cv_.wait(lk, [this] { return not data_.empty(); });
return do_pop();
}
// non-blocking dequeue
std::optional<std::string> try_dequeue() {
ip::scoped_lock<Mutex> lk(mx_);
if (data_.empty())
return std::nullopt;
return do_pop();
}
private:
std::string name_;
Segment sm_;
Mutex& mx_;
Cond& cv_;
DataStructure& data_;
// Assumes mx_ locked by current thread!
std::string do_pop() {
auto&& tmp = std::move(data_.front());
data_.pop_front();
cv_.notify_all(); // any of the waiters might be a/the writer
return std::string(tmp.begin(), tmp.end());
}
};
} // namespace Shared
int main(int argc, char**)
{
Shared::Memory mem("SharedMemory", 10*1024*1024);
switch (argc) {
case 1:
mem.enqueue("Hello");
mem.enqueue("World");
std::cout << "Total queued: " << mem.queue_length() << "\n";
break;
case 2:
std::cout << "Found entries: " << mem.queue_length() << "\n";
while (auto msg = mem.try_dequeue()) {
std::cout << "Dequeued " << *msg << "\n";
}
break;
case 3:
std::cout << "Continuous reader\n";
while (true) {
std::cout << "Dequeued " << mem.dequeue() << "\n";
}
break;
}
}
Little demo:
Summary, Caution
Note there are some loose ends with the above. Notably, the absence of robust locks in Boost Interprocess needs some extra care for proper shutdown without holding the lock.
I'd suggest to contrast with ip::message_queue as well:
How to put file in boost::interprocess::managed_shared_memory? (contrasts shared memory, message_queue and pure TCP sockets)
For error C2664 under msvc mentioned above, it could be solved by changing
data_.emplace_back(std::move(message));
to:
data_.emplace_back(std::move(message.data()));
Hope it could help anyone.
I'm trying to write a thread pool in c++ that fulfills the following criteria:
a single writer occasionally writes a new input value, and once it does, many threads concurrently access this same value, and each spit out a random floating point number.
each worker thread uses the same function, so there's no reason to build a thread-safe queue for all the different functions. I store the common function inside the thread_pool class.
these functions are by far the most computationally-intensive aspect of the program. Any locks that prevent these functions from doing their work is the primary thing I'm trying to avoid.
the floating point output from all these functions is simply averaged.
the user has a single function called thread_pool::start_work that changes this shared input, and tells all the workers to work for a fixed number of tasks.
thread_pool::start_work returns std::future
Below is what I have so far. It can be built and run with g++ test_tp.cpp -std=c++17 -lpthread; ./a.out Unfortunately it either deadlocks or does the work too many (or sometimes too few) times. I am thinking that it's because m_num_comps_done is not thread-safe. There are chances that all the threads skip over the last count, and then they all end up yielding. But isn't this variable atomic?
#include <vector>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <queue>
#include <atomic>
#include <future>
#include <iostream>
#include <numeric>
/**
* #class join_threads
* #brief RAII thread killer
*/
class join_threads
{
std::vector<std::thread>& m_threads;
public:
explicit join_threads(std::vector<std::thread>& threads_)
: m_threads(threads_) {}
~join_threads() {
for(unsigned long i=0; i < m_threads.size(); ++i) {
if(m_threads[i].joinable())
m_threads[i].join();
}
}
};
// how remove the first two template parameters ?
template<typename func_input_t, typename F>
class thread_pool
{
using func_output_t = typename std::result_of<F(func_input_t)>::type;
static_assert( std::is_floating_point<func_output_t>::value,
"function output type must be floating point");
unsigned m_num_comps;
std::atomic_bool m_done;
std::atomic_bool m_has_an_input;
std::atomic<int> m_num_comps_done; // need to be atomic? why?
F m_f; // same function always used
func_input_t m_param; // changed occasionally by a single writer
func_output_t m_working_output; // many reader threads average all their output to get this
std::promise<func_output_t> m_out;
mutable std::shared_mutex m_mut;
mutable std::mutex m_output_mut;
std::vector<std::thread> m_threads;
join_threads m_joiner;
void worker_thread() {
while(!m_done)
{
if(m_has_an_input){
if( m_num_comps_done.load() < m_num_comps - 1 ) {
std::shared_lock<std::shared_mutex> lk(m_mut);
func_output_t tmp = m_f(m_param); // long time
m_num_comps_done++;
// quick
std::lock_guard<std::mutex> lk2(m_output_mut);
m_working_output += tmp / m_num_comps;
}else if(m_num_comps_done.load() == m_num_comps - 1){
std::shared_lock<std::shared_mutex> lk(m_mut);
func_output_t tmp = m_f(m_param); // long time
m_num_comps_done++;
std::lock_guard<std::mutex> lk2(m_output_mut);
m_working_output += tmp / m_num_comps;
m_num_comps_done++;
try{
m_out.set_value(m_working_output);
}catch(std::future_error& e){
std::cout << "future_error caught: " << e.what() << "\n";
}
}else{
std::this_thread::yield();
}
}else{
std::this_thread::yield();
}
}
}
public:
/**
* #brief ctor spawns working threads
*/
thread_pool(F f, unsigned num_comps)
: m_num_comps(num_comps)
, m_done(false)
, m_has_an_input(false)
, m_joiner(m_threads)
, m_f(f)
{
unsigned const thread_count=std::thread::hardware_concurrency(); // should I subtract one?
try {
for(unsigned i=0; i<thread_count; ++i) {
m_threads.push_back( std::thread(&thread_pool::worker_thread, this));
}
} catch(...) {
m_done=true;
throw;
}
}
~thread_pool() {
m_done=true;
}
/**
* #brief changes the shared data member,
* resets the num_comps_left variable,
* resets the accumulator thing to 0, and
* resets the promise object
*/
std::future<func_output_t> start_work(func_input_t new_param) {
std::unique_lock<std::shared_mutex> lk(m_mut);
m_param = new_param;
m_num_comps_done = 0;
m_working_output = 0.0;
m_out = std::promise<func_output_t>();
m_has_an_input = true; // only really matters just after initialization
return m_out.get_future();
}
};
double slowSum(std::vector<double> nums) {
// std::this_thread::sleep_for(std::chrono::milliseconds(200));
return std::accumulate(nums.begin(), nums.end(), 0.0);
}
int main(){
// construct
thread_pool<std::vector<double>, std::function<double(std::vector<double>)>>
le_pool(slowSum, 1000);
// add work
auto ans = le_pool.start_work(std::vector<double>{1.2, 3.2, 4213.1});
std::cout << "final answer is: " << ans.get() << "\n";
std::cout << "it should be 4217.5\n";
return 1;
}
You check the "done" count, then get the lock. This allows multiple threads to be waiting for the lock. In particular, there might not be a thread that enters the second if body.
The other side of that is because you have all threads running all the time, the "last" thread may not get access to its exclusive section early (before enough threads have run) or even late (because additional threads are waiting at the mutex in the first loop).
To fix the first issue, since the second if block has all of the same code that is in the first if block, you can have just one block that checks the count to see if you've reached the end and should set the out value.
The second issue requires you to check m_num_comps_done a second time after acquiring the mutex.
My program is split up into two types of threads: producer, and consumer. The "producer" threads model the production of cars, while the "consumer" threads model the selling of the cars. The idea is for a consumer thread to sell a car as soon as the car is produced, so I'm using a binary semaphore for the consumer thread(s) to wait for a car to be produced, as signalled by each "producer" thread.
Here is my code:
cars.h
/***********************************************************************
* Component
* Cars
* Summary:
* This will deal with the details of working with cars
*
* Note that you must compile this with the -lpthread switch:
* g++ -lpthread lab06.cpp
************************************************************************/
#ifndef CARS_H
#define CARS_H
#include <iostream> // for COUT
#include <queue> // for QUEUE
#include <cassert> // for ASSERT
/*************************************************
* CAR
* A car in the inventory consists of a model and a serial number
************************************************/
struct Car
{
const char * model;
int serialNumber;
};
/******************************************
* DISPLAY CAR
* Display one car in a user-friendly way
****************************************/
inline std::ostream & operator << (std::ostream & out, const Car & car)
{
out << car.model << "(#" << car.serialNumber << ")";
return out;
}
// the number of cars in the production run across all models
#define NUM_CARS 50
// The major models are the following
const char * models[4] =
{
"540",
"570GT",
"650S Spider",
"P1"
};
inline int numModels() { return sizeof(models) / sizeof(models[0]); }
// A sampling of the retailers are the following
const char * retailers[9] =
{
"McLaren Beverly Hills",
"McLaren Monaco",
"McLaren Palm Beach",
"McLaren Philadelphia",
"McLaren Atlanta",
"McLaren Rancho Mirage",
"McLaren Tampa Bay",
"McLaren Paris",
"McLaren Birmingham"
};
inline int numRetailers() { return sizeof(retailers) / sizeof(retailers[0]); }
/*************************************************************
* INVENTORY
* The cars currently in inventory.
* This class is _NOT_ thread-safe and should not be made so
************************************************************/
class Inventory
{
public:
// default constructor: no cars
Inventory() : maxCars(0) {}
// how many cars were in there?
int getMax() const { return maxCars; }
// is the inventory empty?
bool empty() const { return cars.size() == 0 ? true : false; }
// add a car
void makeCar(const Car & car)
{
cars.push(car);
maxCars = (cars.size() > maxCars ? cars.size() : maxCars);
}
// sell a car
Car sellCar()
{
assert(cars.size() > 0);
Car car = cars.front();
cars.pop();
return car;
}
private:
int maxCars; // the biggest the inventory has yet been
std::queue <Car> cars; // the actual cars in the inventory
};
#endif // CARS_H
The lab
#include <iostream> // for COUT
#include <cassert> // for ASSERT
#include <string> // for STRING
#include <sstream> // for STRINGSTREAM
#include <queue> // for QUEUE
#include <ctime> // for time(), part of the random process
#include <unistd.h> // for usleep()
#include <stdlib.h> // for rand() and srand()
#include "cars.h" // for everything dealing with McLaren Cars
#include <semaphore.h>
using namespace std;
// Mutex locks
pthread_mutex_t serialNumberLock;
pthread_mutex_t inventoryLock;
// Semaphore
sem_t semaphore;
// This variable represents the shared memory between the parent thread
// and all the children. Recall from the reading how the main way for
// threads to communicate is through shared memory. This shared memory
// needs to be global.
bool productionComplete = false;
/***********************************************
* PRoDUCER_ARGS
* This stores arguments for the "producer" function.
***********************************************/
struct ProducerArgs
{
const char *model;
int producerNumber=0;
Inventory *inventory;
int beginningSerialNumber=0;
int endingSerialNumber=0;
};
/***********************************************
* CONSUMER_ARGS
* This stores arguments for the "consumer" function.
***********************************************/
struct ConsumerArgs
{
const char* retailer;
Inventory *inventory;
};
void * producer(void *args); // you may need to change this
void * consumer(void *args); // you may need to change this also
int getNumber(const char * prompt, int max);
/***********************************************
* MAIN
* This will serve to prompt the user for the number
* of models and the number of retailers. It will then
* begin the simulation
***********************************************/
int main(int argc, char **argv)
{
// Initialize the semaphore.
sem_init(&semaphore,0,1);
// set up the random number generator
srand(argc == 1 ? time(NULL) : (int)argv[1][1]);
// determine how many producer and consumer threads
int numProducer = getNumber("How many models? ", numModels());
int numConsumer = getNumber("How many retailers? ", numRetailers());
// produce the cars. Note that this code needs to change. We will
// need to launch one thread per producer here
// Consumer threads should fire as soon as a producer produces a car.
Inventory inventory;
pthread_t *producerThreads = new pthread_t[numProducer];
// Divide the work among the threads.
int numCars[4];
int numCarsPerProducer = NUM_CARS / numProducer;
int remainderCarsPerProducer = NUM_CARS % numProducer;
for (int i=0; i < numProducer; i++)
numCars[i] = numCarsPerProducer;
if (remainderCarsPerProducer > 0)
numCars[0] += remainderCarsPerProducer;
int beginningSerialNumber=1;
int endingSerialNumber=0;
ProducerArgs *producerArgs = new ProducerArgs[numProducer];
for (int i = 0; i < numProducer; i++)
{
ProducerArgs *pArgs = &(producerArgs[i]);
pArgs->model = models[i];
pArgs->inventory = &inventory;
pArgs->producerNumber = i;
pArgs->beginningSerialNumber = beginningSerialNumber;
endingSerialNumber += numCars[i];
pArgs->endingSerialNumber = endingSerialNumber;
assert(pArgs->endingSerialNumber <= numProducer * NUM_CARS);
assert(pthread_create(
&(producerThreads[i]),
NULL,
producer,
pArgs
) == 0);
beginningSerialNumber = endingSerialNumber + 1;
}
assert(endingSerialNumber == 50);
// sell the cars. Note that this code also needs to change.
cout << "\nThe cars sold from the various retailers:\n\n";
ConsumerArgs *consumerArgs = new ConsumerArgs[numConsumer];
pthread_t *consumerThreads = new pthread_t[numConsumer];
for (int i = 0; i < numConsumer; i++)
{
ConsumerArgs *args = &(consumerArgs[i]);
args->retailer = retailers[i];
args->inventory = &inventory;
assert(pthread_create(
&(consumerThreads[i]),
NULL,
consumer,
args
) == 0);
};
for (int i=0; i < numConsumer; i++)
{
void *retVal;
pthread_join(consumerThreads[i],&retVal);
string report = *(string *)retVal;
cout << report << endl;
};
// final report
cout << "Maximum size of the inventory: "
<< inventory.getMax()
<< endl;
// Remove arrays from heap.
delete producerArgs;
delete producerThreads;
delete consumerArgs;
delete consumerThreads;
return 0;
}
/***********************************************************
* PRODUCER
* Create those cars.
* This function is not currently thread safe. You will need
* to introduce a critical section in such a way that we do
* not compromise the queue nor produce two cars with the
* same serial number.
**********************************************************/
void *producer(void *args)
{
ProducerArgs *producerArgs = (ProducerArgs *)args;
int producerNumber = producerArgs->producerNumber;
cout << "Producing car " << producerNumber << endl;
const char *model = producerArgs->model;
Inventory *inventory = producerArgs->inventory;
static int serialNumberNext = producerArgs->beginningSerialNumber;
// continue as long as we still need to produce cars in this run
while (serialNumberNext <= producerArgs->endingSerialNumber)
{
// now that we decided to build a car, it takes some time
usleep(rand() % 150000);
// a car to be added to the inventory
Car car;
car.model = model;
car.serialNumber = serialNumberNext;
// add the car to our inventory if we still need to
pthread_mutex_lock(&inventoryLock);
inventory->makeCar(car);
pthread_mutex_unlock(&inventoryLock);
serialNumberNext++;
}
// all done!
productionComplete = true;
assert(!inventory->empty());
// Signal to the consumer threads that production of a car is complete.
sem_post(&semaphore);
pthread_exit(NULL);
}
/***********************************************************
* CONSUMER
* Sell those cars.
* This function is not currently thread safe. You will need
* to introduce a critical section in such a way that we
* do not compromise the queue nor sell the same car twice.
**********************************************************/
void *consumer(void *args)
{
sem_wait(&semaphore);
bool inventoryEmpty;
ConsumerArgs *consumerArgs = (ConsumerArgs *)args;
const char *retailer = consumerArgs->retailer;
Inventory *inventory = consumerArgs->inventory;
// collect our sales history into one string
stringstream sout;
sout << retailer << ":\n";
// continue while there are still customers floating around
// do we have one to sell
pthread_mutex_lock(&inventoryLock);
inventoryEmpty = inventory->empty();
pthread_mutex_unlock(&inventoryLock);
assert(!inventoryEmpty);
// it takes time to sell our car
usleep(rand() % 150000);
cout << "Selling car. " << endl;
Car car = inventory->sellCar();
assert ((car.model != NULL) && (car.model != ""));
sout << car << endl;
// done
string * report = new string(sout.str());
return (void *)report;
}
/*********************************************
* GET NUMBER
* Generic prompt function with error checking
********************************************/
int getNumber(const char * prompt, int max)
{
int value = -1;
assert(cin.good()); // better not already be in error mode
assert(prompt != NULL); // the prompt better be a valid c-string
assert(max > 0); // it better be possible for valid data to exist
// continue prompting until we have valid data
while (value <= 0 || value > max)
{
cout << prompt;
cin >> value;
// if the user typed a non-integer value, reprompt.
if (cin.fail())
{
cin.clear();
cin.ignore();
cout << "Error: non-integer value specified\n";
}
// if the user typed a valid outside the range, reprompt
else if (value <= 0 || value > max)
cout << "Error: value must be between 1 and "
<< max
<< endl;
}
return value;
}
So this is my implementation of a Logical Ring Buffer with Semaphore Synchronization - it is an assignment and most everything here is as described in the book, concerning the implementation of the buffer and semaphores.
Funny thing is this will throw some odd errors most notably Microsoft C++ exception: std::system_error at memory location 0x... for which I can't really find anything online as to what the cause of this is. I assume it might be the accessing of a global variable, but 9/10 times the program will run successfully (assuming I tell VS to continue), the times it does not it is only the last comparison of integers at index 99 of the 2 int arrays.
This is the main method and producer / consumer functions. There is some global variable declaration and initialization, then the Pro/Con threads are created, started, and waited on, finally the results are compared.
#include <iostream> // cout, cin, ignore
#include <thread> // thread, join
#include <random> // rand
/* the number of 'messages' to pass between processes */
const size_t NUMBER_OF_MESSAGES = 100;
/* integer arrays for checking messages after passing */
int PRODUCED_MESSAGES[NUMBER_OF_MESSAGES];
int CONSUMED_MESSAGES[NUMBER_OF_MESSAGES];
/* the logical ring buffer for thread message passing */
LogicalRingBuffer BUFF; // not initiaslized yet ...
void producer_process() {
for (size_t i = 0; i < NUMBER_OF_MESSAGES; i++) {
PRODUCED_MESSAGES[i] = rand();
BUFF.insert(PRODUCED_MESSAGES[i]);
}
}
void consumer_process() {
for (size_t i = 0; i < NUMBER_OF_MESSAGES; i++) {
CONSUMED_MESSAGES[i] = BUFF.remove();
}
}
int main(int agrc, char* argv[]) {
BUFF = LogicalRingBuffer(); /* initializes the buffer */
/* creating the producer and consumer process threads */
std::thread t1(producer_process), t2(consumer_process);
/* wait for both threads to complete before comparisons */
t1.join();
t2.join();
/* iterating through the contents of both integer arrays */
for (size_t i = 0; i < NUMBER_OF_MESSAGES; i++) {
/* printing the contents of the arrays to terminal */
std::cout << "[" << i << "] " << PRODUCED_MESSAGES[i]
<< " <-> " << CONSUMED_MESSAGES[i] << std::endl;
/* inform user and exit program if comparison fails */
if (PRODUCED_MESSAGES[i] != CONSUMED_MESSAGES[i]) {
std::cout << "SYNCHRONIZATION FAILURE!" << std::endl;
std::cin.ignore(); return -1;
}
}
/* inform user of succesful message passing results */
std::cout << "ACTION COMPLETED SUCCESFULLY" << std::endl;
std::cin.ignore(); return 0;
}
And this is the Buffer/Semaphore implementation. I tried to follow the book to a T, and given that almost every time this runs it is successful (other than throwing errors during runtime) I think the synchronicity is stable in this.
struct LogicalRingBuffer{
private:
/* the standard size of the ring buffer */
const static size_t SIZEOF_RING_BUFFER = 10;
/* buffer array and pointers for iteration */
int BUFFER[SIZEOF_RING_BUFFER], *head, *tail;
/* inserts data into the buffer, and recycles the tail pointer */
void push(int data) {
/* insert data into the buffer, increment tail pointer */
*tail = data;
++tail;
/* if tail pointing at end of BUFFER, reset to the front */
if (tail == BUFFER + (SIZEOF_RING_BUFFER - 1)) tail = BUFFER;
}
/* removes data from the buffer, and recycles the head pointer */
int pull() {
/* remove data from the buffer, increment head pointer */
int R = *head;
++head;
/* if head pointing at end of BUFFER, reset to the front */
if (head == BUFFER + (SIZEOF_RING_BUFFER - 1)) head = BUFFER;
/* return the integer data value */
return R;
}
struct Semaphore {
/* the counting value, number of resources */
int count{ NULL };
/* examines resources, holds until ready */
void wait() {
while (count <= 0); //busy wait
--count;
}
/* releases aquired resource (increment) */
void signal() {
++count;
}
} empty, full, mutex; /* Semaphores for Synchronization */
public:
/* initializer for LogicalRingBuffer struct */
LogicalRingBuffer() {
head = tail = BUFFER; // all pointers at BUFFER[0]
empty.count = SIZEOF_RING_BUFFER; // number of open positions
mutex.count = 1; // a binary semaphore, mutex
full.count = 0; // number of used positions
}
/* semaphore synchronized insertion of data */
void insert(int data) {
empty.wait(); // decrements available positions in buff
mutex.wait(); // waits to gain mutual exlusion lock
push(data); // pushes the data into the ring buff
mutex.signal(); // releases the mutual exclusion lock
full.signal(); // increments count of buffered datums
}
/* semaphore synchronized removal of data */
int remove() {
int data{ NULL }; // initialize return data
full.wait(); // decrements count of buffered items
mutex.wait(); // waits to gain mutual exlusion lock
data = pull(); // pulls the data from the ring buff
mutex.signal(); // releases the mutual exlusion lock
empty.signal(); // increments avilable positions in buff
return data; // return integer data
}
};
I guess I just want this to run without any hiccups, so am I missing something here? Because I'm fairly certain the logic is correct and this is something that maybe Visual Studio does, or who knows...
The two threads you create both attempt to lock the mutex by calling Semaphore::wait(). However, there is nothing preventing the threads from reading and writing to the mutex's count variable at the same time. This results in undefined behavior which can cause the system errors you are seeing. You are on the right track in your implementation, but at some point the program has to go to the OS for actual thread-safety. You need to use either the standard library's thread-safety mechanisms to protect concurrent access to this variable or your OS's low-level mechanisms, like Windows's critical sections.
C++11's standard library implements std::mutex which can be used to protect concurrent access to variables which are shared between threads. However, it's implementation is likely as high-level as the one you are trying to implement yourself, so it would make more sense to discard your own implementation in favor of the standard library's.
This was my solution to the problem, compiling in Cygwin GCC with: g++ *.cpp -std=c++11 -w and rewriting the Semaphore::wait() method as follows:
void wait() {
/* the next line was added to avoid count being accessed simultaneously, vvv */
std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); // <---
while (count <= 0); /* BUSY WAIT */
--count;
}
I plan on running more tests, but so far I have had exactly 0 conflicts when using the thread sleep.
I am trying to write a logger class for my C++ calculator, but I'm experiencing a problem while trying to push a string into a list.
I have tried researching this issue and have found some information on this, but nothing that seems to help with my problem. I am using a rather basic C++ compiler, with little debugging utilities and I've not used C++ in quite some time (even then it was only a small amount).
My code:
#ifndef _LOGGER_H_
#define _LOGGER_H_
#include <iostream>
#include <list>
#include <string>
using std::cout;
using std::cin;
using std::endl;
using std::list;
using std::string;
class Logger
{
private:
list<string> mEntries;
public:
Logger() {}
~Logger() {}
// Public Methods
void WriteEntry(const string& entry)
{
mEntries.push_back(entry);
}
void DisplayEntries()
{
cout << endl << "**********************" << endl
<< "* Logger Entries *" << endl
<< "**********************" << endl
<< endl;
for(list<string>::iterator it = mEntries.begin();
it != mEntries.end(); it++)
{
// *** BELOW LINE IS MARKED WITH THE ERROR ***
cout << *it << endl;
}
}
};
#endif
I am calling the WriteEntry method by simply passing in a string, like so:
mLogger->WriteEntry("Testing");
Any advice on this would be greatly appreciated.
* CODE ABOVE HAS BEEN ALTERED TO HOW IT IS NOW *
Now, the line:
cout << *it << endl;
causes the same error. I'm assuming this has something to do with how I am trying to get the string value from the iterator.
The code I am using to call it is in my main.cpp file:
#include <iostream>
#include <string>
#include <sstream>
#include "CommandParser.h"
#include "CommandManager.h"
#include "Exceptions.h"
#include "Logger.h"
using std::string;
using std::stringstream;
using std::cout;
using std::cin;
using std::endl;
#define MSG_QUIT 2384321
#define SHOW_LOGGER true
void RegisterCommands(void);
void UnregisterCommands(void);
int ApplicationLoop(void);
void CheckForLoggingOutput(void);
void ShowDebugLog(void);
// Operations
double Operation_Add(double* params);
double Operation_Subtract(double* params);
double Operation_Multiply(double* params);
double Operation_Divide(double* params);
// Variable
CommandManager *mCommandManager;
CommandParser *mCommandParser;
Logger *mLogger;
int main(int argc, const char **argv)
{
mLogger->WriteEntry("Registering commands...\0");
// Make sure we register all commands first
RegisterCommands();
mLogger->WriteEntry("Command registration complete.\0");
// Check the input to see if we're using the program standalone,
// or not
if(argc == 0)
{
mLogger->WriteEntry("Starting application message pump...\0");
// Full version
int result;
do
{
result = ApplicationLoop();
} while(result != MSG_QUIT);
}
else
{
mLogger->WriteEntry("Starting standalone application...\0");
// Standalone - single use
// Join the args into a string
stringstream joinedStrings(argv[0]);
for(int i = 1; i < argc; i++)
{
joinedStrings << argv[i];
}
mLogger->WriteEntry("Parsing argument '" + joinedStrings.str() + "'...\0");
// Parse the string
mCommandParser->Parse(joinedStrings.str());
// Get the command names from the parser
list<string> commandNames = mCommandParser->GetCommandNames();
// Check that all of the commands have been registered
for(list<string>::iterator it = commandNames.begin();
it != commandNames.end(); it++)
{
mLogger->WriteEntry("Checking command '" + *it + "' is registered...\0");
if(!mCommandManager->IsCommandRegistered(*it))
{
// TODO: Throw exception
mLogger->WriteEntry("Command '" + *it + "' has not been registered.\0");
}
}
// Get each command from the parser and use it's values
// to invoke the relevant command from the manager
double results[commandNames.size()];
int currentResultIndex = 0;
for(list<string>::iterator name_iterator = commandNames.begin();
name_iterator != commandNames.end(); name_iterator++)
{
string paramString = mCommandParser->GetCommandValue(*name_iterator);
list<string> paramStringArray = StringHelper::Split(paramString, ' ');
double params[paramStringArray.size()];
int index = 0;
for(list<string>::iterator param_iterator = paramStringArray.begin();
param_iterator != paramStringArray.end(); param_iterator++)
{
// Parse the current string to a double value
params[index++] = atof(param_iterator->c_str());
}
mLogger->WriteEntry("Invoking command '" + *name_iterator + "'...\0");
results[currentResultIndex++] =
mCommandManager->InvokeCommand(*name_iterator, params);
}
// Output all results
for(int i = 0; i < commandNames.size(); i++)
{
cout << "Result[" << i << "]: " << results[i] << endl;
}
}
mLogger->WriteEntry("Unregistering commands...\0");
// Make sure we clear up our resources
UnregisterCommands();
mLogger->WriteEntry("Command unregistration complete.\0");
if(SHOW_LOGGER)
{
CheckForLoggingOutput();
}
system("PAUSE");
return 0;
}
void RegisterCommands()
{
mCommandManager = new CommandManager();
mCommandParser = new CommandParser();
mLogger = new Logger();
// Known commands
mCommandManager->RegisterCommand("add", &Operation_Add);
mCommandManager->RegisterCommand("sub", &Operation_Subtract);
mCommandManager->RegisterCommand("mul", &Operation_Multiply);
mCommandManager->RegisterCommand("div", &Operation_Divide);
}
void UnregisterCommands()
{
// Unregister each command
mCommandManager->UnregisterCommand("add");
mCommandManager->UnregisterCommand("sub");
mCommandManager->UnregisterCommand("mul");
mCommandManager->UnregisterCommand("div");
// Delete the logger pointer
delete mLogger;
// Delete the command manager pointer
delete mCommandManager;
// Delete the command parser pointer
delete mCommandParser;
}
int ApplicationLoop()
{
return MSG_QUIT;
}
void CheckForLoggingOutput()
{
char answer = 'n';
cout << endl << "Do you wish to view the debug log? [y/n]: ";
cin >> answer;
switch(answer)
{
case 'y':
ShowDebugLog();
break;
}
}
void ShowDebugLog()
{
mLogger->DisplayEntries();
}
// Operation Definitions
double Operation_Add(double* values)
{
double accumulator = 0.0;
// Iterate over all values and accumulate them
for(int i = 0; i < (sizeof values) - 1; i++)
{
accumulator += values[i];
}
// Return the result of the calculation
return accumulator;
}
double Operation_Subtract(double* values)
{
double accumulator = 0.0;
// Iterate over all values and negativel accumulate them
for(int i = 0; i < (sizeof values) - 1; i++)
{
accumulator -= values[i];
}
// Return the result of the calculation
return accumulator;
}
double Operation_Multiply(double* values)
{
double accumulator = 0.0;
for(int i = 0; i < (sizeof values) - 1; i++)
{
accumulator *= values[i];
}
// Return the value of the calculation
return accumulator;
}
double Operation_Divide(double* values)
{
double accumulator = 0.0;
for(int i = 0; i < (sizeof values) - 1; i++)
{
accumulator /= values[i];
}
// Return the result of the calculation
return accumulator;
}
Did you remember to call mLogger = new Logger at some point? Did you accidantally delete mLogger before writing to it?
Try running your program in valgrind to see whether it finds any memory errors.
After your edit, the solution seem clear:
Your first line in main() is :
mLogger->WriteEntry("Registering commands...\0");
Here mLogger is a pointer that has never been initialized. This is "undefined behaviour", meaning anything can appen, often bad things.
To fix this you can either make it a "normal" variable, not a pointer or create a Logger instance using new (either at the declaration or as the first line in main).
I suggest you to not use a pointer to be sure the logger is always there and is automatically destroyed.
By the way, it seems like you want to create every instance of objects on the heap using pointers. It's not recommanded if it's not necessary. You should use pointers ONLY if you want to explicitely state the creation (using new) and destruction (using delete) of the instance object. If you just need it in a specific scope, don't use a pointer. You might come from another language like Java or C# where all objects are referenced. If so, you should start learning C++ like a different language to avoid such kind of problem. You should learn about RAII and other C++ scpecific paradigm that you cannot learn in those languages. If you come from C you should too take it as a different language. That might help you avoid complex problems like the one you showed here. May I suggest you read some C++ pointer, references and RAII related questions on stackoverflow.
First, you don't need to create the std::list on the heap. You should just use it as a normal member of the class.
class Logger
{
private:
list<string> mEntries; // no need to use a pointer
public:
Logger() // initialization is automatic, no need to do anything
{
}
~Logger() // clearing and destruction is automatic too, no need to do anything
{
}
//...
};
Next, entryData don't exist in this code so I guess you wanted to use entry. If it's not a typo then you're not providing the definition of entryData that is certainly the source of your problem.
In fact I would have written your class that way instead:
class Logger
{
private:
list<string> mEntries;
public:
// no need for constructor and destructor, use the default ones
// Public Methods
void WriteEntry(const string& entry) // use a const reference to avoid unnecessary copy (even with optimization like NRVO)
{
mEntries.push_back( entry ); // here the list will create a node with a string inside, so this is exactly like calling the copy constructor
}
void DisplayEntries()
{
cout << endl << "**********************" << endl
<< "* Logger Entries *" << endl
<< "**********************" << endl
<< endl;
for(list<string>::iterator it = mEntries.begin();
it != mEntries.end(); ++it) // if you want to avoid unnecessary copies, use ++it instead of it++
{
cout << *it << endl;
}
}
};
What's certain is that your segfault is from usage outside of this class.
Is an instance of Logger being copied anywhere (either through a copy constructor or operator=)? Since you have mEntries as a pointer to a list, if you copy an instance of Logger, they will share the value of the pointer, and when one is destructed, it deletes the list. The original then has a dangling pointer. A quick check is to make the copy constructor and operator= private and not implemented:
private:
void operator=(const Logger &); // not implemented
Logger(const Logger &); // not implemented
When you recompile, the compiler will flag any copies of any Logger instances.
If you need to copy instances of Logger, the fix is to follow the Rule of 3:
http://en.wikipedia.org/wiki/Rule_of_three_%28C%2B%2B_programming%29
You can do this by eliminating the need for the destructor (by not using a pointer: list<string> mEntries), or by adding the needed code to the copy constructor and operator= to make a deep copy of the list.
You only need to do
list<string> entries;
entries.push_back();
You do not need to create a pointer to entries.
Nothing too obvious, though you typed
mEntries->push_back(string(entryData));
and I htink you meant entry instead of entryData. You also don't need the string conversion on that line, and your function should take entry by const reference.
However, none of these things would cause your program to segfault. What compiler are you using?
You're missing the copy constructor. If the Logger object is copied and the original deleted, you'll be dereferencing memory that was previously deleted.
A simplified example of the problem
Logger a;
{
Logger b;
a=b;
}
a.WriteEntry("Testing");
Add a copy constructor.
Logger(const Logger& item)
{
mEntries = new list<string>();
std::copy(item.mEntries->begin(), item.mEntries->end(), std::back_inserter(*mEntries));
}