3
\$\begingroup\$

This is one of tasks, which I had to do for the recruitment process to be started in some company. Unfortunately they didn't like it so I've decided to share it here for discussion.

Philo5.h

#ifndef PHILO5_H_
#define PHILO5_H_

//Number of philosophers
const int PHIL_COUNT = 5;

//How many times each philosopher will eat
const int SEQ_COUNT = 5;

class CFork
{
    pthread_mutex_t _lock;

public:

    CFork();

    virtual ~CFork();

    void acquire();

    void release();

};

class CBoard
{
    pthread_mutex_t _lock;

public:
    explicit CBoard();

    virtual ~CBoard();

    void showChar(int no, char c);
};

class CPhilo
{
    int         _no;
    pthread_t   _thread;
    int         _tid;
    CFork*      _left;
    CFork*      _right;
    CBoard*     _board;

public:

    explicit CPhilo(int no, CFork* left, CFork* right, CBoard* board);

    virtual ~CPhilo();

    static void* run(void * context);

    void acquire();

    void release();

    void think();

    void eat();

    void waitForLeft();

    void waitForRight();

    void releaseLeft();

    void releaseRight();

    int getNo();

    void setNo(int no);

    bool amIOdd();

    void join();

};


//A class demonstrating problem of 5 philosophers
class CPhilo5
{
    CPhilo* philos[PHIL_COUNT];
    CFork * forks[PHIL_COUNT];
    CBoard* _board;

public:

    CPhilo5();

    ~CPhilo5();

    void join();

private:

    void createForks();

    void createPhilosophers();

};


#endif /* PHILO5_H_ */

Philo5.cpp

#include <pthread.h>
#include <unistd.h> //for sleep
#include <cstdlib>  //for rand
#include <iostream>

#include "Philo5.h"

using namespace std;

CFork::CFork()
{
    pthread_mutex_init(&_lock, NULL);
}

CFork::~CFork()
{
    pthread_mutex_destroy(&_lock);
}

void CFork::acquire()
{
    pthread_mutex_lock(&_lock);
}

void CFork::release()
{
    pthread_mutex_unlock(&_lock);
}

//----------------------------------------------------------------------
CBoard::CBoard()
{
    pthread_mutex_init(&_lock, NULL);
}

CBoard::~CBoard()
{
    pthread_mutex_destroy(&_lock);
}

void CBoard::showChar(int no, char c)
{
    pthread_mutex_lock(&_lock);
    for(int i=0; i<no; i++)
    {
        cout << "  ";
    }
    cout << no+1 << c << endl;
    pthread_mutex_unlock(&_lock);
}

//----------------------------------------------------------------------
CPhilo::CPhilo(int no, CFork* left, CFork* right, CBoard* board)
: _no(no)
, _left(left)
, _right(right)
, _board(board)
{
    _board->showChar(_no, 'C');
    _tid = pthread_create(&_thread, NULL, &CPhilo::run, this);
}

CPhilo::~CPhilo()
{
}

void* CPhilo::run(void * context)
{
    CPhilo* p = static_cast<CPhilo*>(context);

    for(int i=0; i<SEQ_COUNT; i++)
    {
        p->think();
        p->acquire();
        p->eat();
        p->release();
    }
    return NULL;
}

void CPhilo::acquire()
{
    if(amIOdd())
    {
        waitForRight();
        waitForLeft();
    }
    else
    {
        waitForLeft();
        waitForRight();
    }
}

void CPhilo::release()
{
    releaseLeft();
    releaseRight();
}

void CPhilo::think()
{
    _board->showChar(_no, 'T');
    sleep(1);
}

void CPhilo::eat()
{
    _board->showChar(_no, 'E');
    sleep(2);
}

void CPhilo::waitForLeft()
{
    _left->acquire();
}

void CPhilo::waitForRight()
{
    _right->acquire();
}

void CPhilo::releaseLeft()
{
    _left->release();
}

void CPhilo::releaseRight()
{
    _right->release();
}

int CPhilo::getNo()
{
    return _no;
}

void CPhilo::setNo(int no)
{
    _no = no;
}

bool CPhilo::amIOdd()
{
    return static_cast<bool>(_no % 2);
}

void CPhilo::join()
{
    void* result;
    pthread_join(_thread, &result);
}

//----------------------------------------------------------------------
CPhilo5::CPhilo5()
{
    _board = new CBoard();
    createForks();
    createPhilosophers();
}

void CPhilo5::createForks()
{
    for(int i = 0; i<PHIL_COUNT; i++)
    {
        forks[i] = new CFork();
    }
}

void CPhilo5::createPhilosophers()
{
    for(int i = 0; i<PHIL_COUNT; i++)
    {
        philos[i] = new CPhilo(i, forks[i], forks[(i+1)%5], _board);
    }
}

CPhilo5::~CPhilo5()
{
    for(int i = 0; i<PHIL_COUNT; i++)
    {
        delete philos[i];
        delete forks[i];
    }
    delete _board;
}

void CPhilo5::join()
{
    for(int i=0; i<PHIL_COUNT; i++)
    {
        philos[i]->join();
    }
}

Why could the solution have been disliked by the recruiters?

The first thing I regret is that didn't split the classes to separate files, however I don't think this was the main reason for rejection...

\$\endgroup\$
  • 2
    \$\begingroup\$ Perhaps you could stop posting these questions until your current ones are reviewed; otherwise, reviewers may just tell you the same things for each question. \$\endgroup\$ – Hosch250 Jun 27 '15 at 23:14
  • \$\begingroup\$ I wanted to share the code unchanged - the same as sent to that company - because I wanted to know why it wasn't accepted. If I changed the other tasks after getting the first comments from you, I would probably not catch the reason for rejection. \$\endgroup\$ – Sean Jun 29 '15 at 12:29
3
\$\begingroup\$

Don't use all caps for language scope identifiers.

const int PHIL_COUNT = 5;

By convention identifiers in all caps are reserved for the pre-processor and have the potential to be stomped all over.

Stop using new

_board = new CBoard();

Why this can just as easily be an automatic variable. If I was reading this as part of your job application. I would just throw your resume away (not even file it for later consideration).

  • Nearly all your variables can be automatic and thus not have any of the memory management issues.
  • Also you are not using standard memory management techniques to control your dynamically allocated memory (thing std::shared_ptr and std::unqiue_ptr).
  • There is not "Separation of Concerns" in your class. A class should either do business logic or it should do resource management it should never do both.
  • You don't implement the rule of three/five.

Threading

Sure you can use the C language threading model. But C++ has its own threading model you should probably look into.

 std::thread  thread;

Prefer reference over pointers

CFork*      _left;
CFork*      _right;
CBoard*     _board;

All these are guaranteed not to be NULL. So why have pointers. Reference are a much better metaphor for what you are trying to achieve and there is no need to validate they are NULL.

Public Interface

These are all part of the public interface?

void acquire();
void release();
void think();
void eat();
void waitForLeft();
void waitForRight();
void releaseLeft();
void releaseRight();
int getNo();
void setNo(int no);
bool amIOdd();
void join();

Overall Design

Is wrong. If a philosopher can not acquire either fork they are supposed to be thinking instead. In your code when they try and acquire a fork they block waiting for the to be available. Thus they never get any thinking time in.

When you try and acquire a fork if you fail you drop any currently held forks and then think. You should never block waiting for fork.

I don't remember and dinner parties were guests were just looking at the handle of the fork waiting for it to become available. If they could not get a fork they generally engage in conversation until the fork is available.

Remember this problem was originally designed to allocate tape drive resources as efficiently as possible. You are not using your tape drives or your CPU very well if you spend time waiting.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.