4 ms·
Solving Likely Problems in Your Multithreaded Code
- taspeotis 12y agoMicrosoft's code quality evangelism pains me a bit. With one hand they publish articles like this and add tools to Visual Studio to help with code quality (e.g. adding static code analysis to VS2012 Express and later). And with the other hand they go "haha fuck you" and arbitrarily lock out some code quality tools unless you're using Visual Studio Ultimate. (my Visual Studio with MSDN Premium isn't enough, it seems.) "Smart Unit Tests" [1] is the most recent in a long list of things tucked away in Ultimate. [1] https://msdn.microsoft.com/library/dn823749(v=vs.140).aspx https://msdn.microsoft.com/library/dn823749(v=vs.140).aspx
- copsarebastards 12y agoThis isn't surprising. Microsoft is out to make money, not to make the world's code quality better. They may make the world's code quality better sometimes, but if they are faced with a choice between the two, they're going to choose the money. Visual Studio's tiered pricing and tiered features are just an example of that. If making the world's code quality better is a priority for you, I think the solution is to switch to free languages and toolchains and help improve them. I'm not saying this from a "shut up or leave" kind of mentality: this is exactly why I moved out of the C#/.NET ecosystem.
- rayiner 12y agoThe bank account example is a classic illustration of the problems with locking: class BankAccount { internal static void Transfer( BankAccount a, BankAccount b, decimal delta) { Withdraw(a, delta); Deposit(b, delta); } // As before } Withdraw() and Deposit() either do their own locking, and we get a race trying to use them together, or we push locking up the chain to their users. The former solution is unacceptable, and the latter just punts the problem--what if we want to call Transfer() and some other function together without a possible race condition between them?
- taspeotis 12y ago> what if we want to call Transfer() and some other function together without a possible race condition between them? Assuming you've got some sort of database or something that has a notion of a transaction use a TransactionScope [1] with something like IsolationLevel.RepeatableRead. They are quite flexible. They are created with thread affinity [2] so any operation on the same thread can detect their presence and enlist in them. Make a TransactionScope, call your methods in whichever order you want, commit it. [1] https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28v=vs.110%29.aspx https://msdn.microsoft.com/en-us/library/system.transactions... [2] Sort of. They can be shared between threads but the easiest way to use them is to treat them like they're bound to a thread.
- lmm 12y agoCreate a "box" type, say AccountAction, that represents something that has to happen atomically. The AccountAction-ness ripples up the chain, so user functions may return AccountActions, or may call several AccountActions and return a single combined AccountAction for things like this Transfer. At the top level there can be a single "actor" that's responsible for processing accounts that processes AccountActions one at a time; user code passes an AccountAction[T] and gets back the "unboxed" T, the actor having performed the action atomically. Ensure that the only way to construct something that accesses a BankAccount is as an AccountAction. You're automatically assured that any code calls the "actor" eventually, because there's no way to e.g. print an AccountAction[String] to standard output - sooner or later you have to make the call. User code can still have races if it tries (e.g. if it deliberately calls the "actor" separately for the withdraw and the deposit), but such code is much more obviously wrong from reading and looking at the types (particularly if you call your actor call something like "unsafeTransaction"). If you've got a single consistent point of entry/exit (e.g. this is a message-processing system), you can put the actor call there. The only hard part is making this easy enough to use that it doesn't overcomplicate the code. Provide easy syntax for composing two AccountActions - something that's as clear and simple as just calling two methods, but with just enough difference to make it clear from the code whether you're using one or not - e.g. non-action calls use "=", action calls use "<-". Make a library of functions for doing things like "perform a list of AccountActions as a single, atomic AccountAction". Better yet, make AccountAction conform to some interface such that that library already exists. (Of course by now you've probably guessed that AccountAction is just an alias for the reader monad. But the naming is valuable; it keeps everything more concrete and clear)
- pc86 12y ago(2008)