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

[WIP] Implement exercise bank-account #757

Closed
wants to merge 10 commits into from

Conversation

thecouchcoder
Copy link
Contributor

@thecouchcoder thecouchcoder commented Oct 7, 2017


Resolves #730

Closes #1260 (mutual-exclusion)

@ilya-khadykin ilya-khadykin changed the title Implement exercise bank-account [WIP] Implement exercise bank-account Oct 7, 2017
@thecouchcoder thecouchcoder force-pushed the bank-account branch 3 times, most recently from 1998c23 to dc4c1f5 Compare October 9, 2017 22:41
config.json Outdated
"unlocked_by": null,
"difficulty": 4,
"topics": [
"Classes",
Copy link
Contributor

Choose a reason for hiding this comment

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

We've switched to snake_case naming convention for topics to increase consistency among tracks, so could you please choose them from the following list - https://github.com/exercism/problem-specifications/blob/master/TOPICS.txt

Copy link
Contributor

@ilya-khadykin ilya-khadykin left a comment

Choose a reason for hiding this comment

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

Good work!

I left some comments, please take a look


def getBalance(self):
self.lock.acquire()
if (self.open is True):
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably just:

if self.open:
    # do stuff

return self.balance
else:
self.lock.release()
raise Exception
Copy link
Contributor

@ilya-khadykin ilya-khadykin Oct 11, 2017

Choose a reason for hiding this comment

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

Could you please throw a narrower Exception, or at least state the root cause by:

raise Exception('Describe what went wrong') 

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps ValueError would be better?


class BankAccount(object):
def __init__(self):
self.lock = threading.Lock()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be a good idea to initialize open flag in constructor as well:

self.open = False

@thecouchcoder
Copy link
Contributor Author

Thank you @m-a-ge I will get on these as soon as I have a chance :)

Copy link
Contributor

@cmccandless cmccandless left a comment

Choose a reason for hiding this comment

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

@aes421 Thanks for your PR! I've left some comments, please read them.

self.account.open()
self.account.close()

with self.assertRaises(Exception):
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest something more specific than Exception; perhaps ValueError?

return self.balance
else:
self.lock.release()
raise Exception
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps ValueError would be better?

@N-Parsons
Copy link
Contributor

Hi @aes421, are you still interesting in working on this?

@thecouchcoder
Copy link
Contributor Author

@N-Parsons Life got in the way of coding for a few weeks. About to work on some of these fixes right now!

@thecouchcoder
Copy link
Contributor Author

@cmccandless Is there anything else I need to do?

@cmccandless
Copy link
Contributor

@aes421 Not as far as I can tell, but since @m-a-ge previously had comments on this, I'd like to give him the chance to comment again.


It should be possible to close an account; operations against a closed account must fail.

### Submitting Exercises
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be at heading level 2 (##).

def __init__(self):
pass

def getBalance(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Following PEP8 we use snake_case for function and method names, so this should be get_balance.

thread = threading.Thread(target=increment_and_decrement,
args=(self, ))
threadlist.append(thread)
i += 1
Copy link
Contributor

@N-Parsons N-Parsons Nov 17, 2017

Choose a reason for hiding this comment

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

This loop would be better implemented using for _ in range(100):.

Copy link
Contributor

@N-Parsons N-Parsons left a comment

Choose a reason for hiding this comment

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

There are a couple of minor issues, but I think there's a very significant issue regarding concurrency.

with self.assertRaises(ValueError):
self.account.deposit(-50)

def test_can_handle_concurrent_transactions(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

@aes421 (cc: @cmccandless, @m-a-ge)

I'm not sure that this test is truly testing for concurrency - depositing and then immediately withdrawing inherently safe values within one function is never going to break as far as I can see. Further, I can remove all instances of self.lock from the example code and the tests still pass. Part of the issue is probably that the Global Interpreter Lock (GIL) already locks the BankAccount instance when a thread accesses it, and releases it once the thread ends. I'm not sure of the best way to get around this.

Currently, Python seems to be running these threads in order, depositing 10 and withdrawing 1, then moving on to the next thread. We can see this by storing a list of transaction values and using i+1 as a second argument for the increment_and_decrement function, depositing and withdrawing this value, and then printing the transaction list after all threads are done.

One possible solution to get disorder seems to be to add time.sleep(random.random()) before deposits or withdrawals, but I think the deposits and withdrawal should be decoupled and not part of the same function call - just joining a list of threads for each should be sufficient if there are random delays.

Concurrency was discussed a little in #1106. If we want to keep concurrency in this exercise (I think the exercise would be ok without it, but only as an early exercise about classes), I think that some consideration needs to be made for how to make the tests only pass if it is safe concurrent implementation.

Ultimately, for concurrency to make sense here, we need to be able to make it so that the concurrency matters in some way.

Copy link
Contributor

Choose a reason for hiding this comment

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

@N-Parsons (cc: @aes421, @m-a-ge)

I think the exercise would be ok without it

I disagree. It's a core part of the exercise. From the problem description:

Create an account that can be accessed from multiple threads/processes (terminology depends on your programming language).

If we marked another exercise as foregone simply because of the challenges of testing parallelism in Python, then I think we might need to consider foregoing this exercise as well. Either that, or we need to come up with another solution and apply it both to this exercise and the foregone parallel-letter-frequency.

Copy link
Contributor

@Dog Dog Mar 1, 2018

Choose a reason for hiding this comment

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

If we want to be pedantic with this exercise, we may be able to do something similar to NumPy and write an extension in C that runs outside of the GIL. However we can only safely do that if we use no Python C API functions.

I also want to quickly put a note here because I don't think concurrency and parallelism have been discussed much in this repo. There is a difference between concurrency and parallelism.

"In programming, concurrency is the composition of independently executing processes, while parallelism is the simultaneous execution of (possibly related) computations." - from golang.org

https://vimeo.com/49718712 (Video of Rob Pike talking about the difference at Heroku)

Copy link
Contributor

@Dog Dog Mar 1, 2018

Choose a reason for hiding this comment

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

We also might be able to use this:

https://docs.python.org/2/library/multiprocessing.html

@stale
Copy link

stale bot commented Dec 8, 2017

This issue has been automatically marked as abandoned because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the abandoned label Dec 8, 2017
@stale stale bot removed the abandoned label Dec 8, 2017
@cmccandless
Copy link
Contributor

Pinning this (pending further discussion on concurrency).

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.

5 participants