Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CPP solution to P1 #1

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

CPP solution to P1 #1

wants to merge 7 commits into from

Conversation

VipAlekseyPetrenko
Copy link
Owner

No description provided.

@jetvova
Copy link

jetvova commented Jun 7, 2024

Pretty good PR. The helper function to ensure ordering is a nice approach that helps reduce the amount of copying needed. Consider doing it for java also, by making a dedicated "wrapper" function to which Runnable printFirst/Second/Third is passed to.

Copy link

@maxokey maxokey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java solution looks good to me, the cv cpp one needs to be changed as it doesn't guarantee a consistent output. Didn't comment on the mutex one because we should be using cv for the solution.

Week_1/P1_cv.cpp Outdated

void orderPrints(int number) {
unique_lock<mutex> lock(myMutex);
while(number != currIndex) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use the wait with a predicate function as an argument (lock(myMutex, [this, number]() { return number == currIndex; });) instead of this while loop, both are valid though

Week_1/P1_cv.cpp Outdated
}

void second(function<void()> printSecond) {
orderPrints(2);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After you return from line 27, you are no longer holding the lock, thus the further logic of your function might be interleaved with other threads'. For example, you might have the following order of execution: orderPrints(1), orderPrints(2), and then a concurrent execution of printFirst and printSecond, resulting in gibberish in stdout.

Week_1/P1.java Outdated
@@ -0,0 +1,35 @@
class Foo {
private Object lock = new Object();
volatile private int currIndex = 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary volatile. confusing, as it suggest your intention to access this without a lock

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it

Week_1/P1.java Outdated

public void first(Runnable printFirst) throws InterruptedException {
synchronized(lock) {
printFirst.run();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, calling a callback with code you have no control of inside a lock, bad things can happen. Most importantly - it makes local reasoning impossible - so no way to judge if this code correct or not without looking at the whole program

Week_1/P1_cv.cpp Outdated
void first(function<void()> printFirst) {
orderPrints(1);
printFirst();
++currIndex;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BUG: shared state is modified outside a lock. It's not a nitpicking but an actual bug making this code incorrect and in some environments it might behave not as you expect


bool orderPrints(int number) {
myMutex.lock();
if (currIndex == number) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BUG: this should be a while loop not a single-shot if. Think why you ALWAYS have to use while.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood

}

void first(function<void()> printFirst) {
while(!orderPrints(1));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh ... you have a while loop here. While this addresses previous comment, this code structure complicates the understanding and makes more difficult to show why it's correct

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood

void first(function<void()> printFirst) {
while(!orderPrints(1));
printFirst();
myMutex.unlock();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very hard to reason about correctness of this code -> there's no pairing lock in the same scope.

If you really need to unlock (which is almost never neeeded) in a different scope - consider C++'s unique_lock which provides you a. scope - and you can std::move it - e.g. if function establishes a lock - you can return unique_lock and then calling scope will own it. But I'm actually not encouraging to do this - try to use scope-based locks - otherwise it's very hard to reason about correctness

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I wasn't sure how I can get a lock in my wrapper function, and then keep this lock until I finish some work in some other function. However, after now that I know a way to most all my locking/unlocking logic into the wrapper function, I will use scoped locks from now on.

mutex myMutex;
int currIndex = 1;

bool orderPrints(int number) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for attempt to factor out common code - if you do it right that helps to verify correctness of the program, but here are two issues. orderPrints() isn't a very good name choice, and second - (see in other comments)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I thought that factoring out code with critical sections is a good idea :)
I agree about the naming. If fact, if you have a good source -- please share how to name my functions "right" without having to guess.

Week_1/P1.cpp Outdated
void printsWrapper(function<void()> printFunction, int turn) {
unique_lock<mutex> lock(gMutex);
gCv.wait(lock, [this, turn]{ return turn == currTurn; });
printFunction();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

red flag - you unvoke printFunction() [on which implementation you don't have any control] under the lock.
Since you have no control on what that function does or what it can do when it is independently changed at some point in future - you can't apply "local reasoning" principle to judge about correctness of your code - and to assert correctness you would need to look at the whole program.

void third(function<void()> printThird) {
printsWrapper(printThird, 3);
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add here a proof of correctness in a long comment. I'll follow up with feedback once others submit the code and peer-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants