-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Conversation
1998c23
to
dc4c1f5
Compare
…nd adding a deposit
dc4c1f5
to
34d7f5a
Compare
config.json
Outdated
"unlocked_by": null, | ||
"difficulty": 4, | ||
"topics": [ | ||
"Classes", |
There was a problem hiding this comment.
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
There was a problem hiding this 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
exercises/bank-account/example.py
Outdated
|
||
def getBalance(self): | ||
self.lock.acquire() | ||
if (self.open is True): |
There was a problem hiding this comment.
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
exercises/bank-account/example.py
Outdated
return self.balance | ||
else: | ||
self.lock.release() | ||
raise Exception |
There was a problem hiding this comment.
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')
There was a problem hiding this comment.
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?
exercises/bank-account/example.py
Outdated
|
||
class BankAccount(object): | ||
def __init__(self): | ||
self.lock = threading.Lock() |
There was a problem hiding this comment.
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
Thank you @m-a-ge I will get on these as soon as I have a chance :) |
There was a problem hiding this 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): |
There was a problem hiding this comment.
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
?
exercises/bank-account/example.py
Outdated
return self.balance | ||
else: | ||
self.lock.release() | ||
raise Exception |
There was a problem hiding this comment.
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?
Hi @aes421, are you still interesting in working on this? |
@N-Parsons Life got in the way of coding for a few weeks. About to work on some of these fixes right now! |
@cmccandless Is there anything else I need to do? |
@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 |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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):
.
There was a problem hiding this 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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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:
This issue has been automatically marked as |
Pinning this (pending further discussion on concurrency). |
Resolves #730
Closes #1260 (mutual-exclusion)