digitalmars.D.learn - Asynchronous concurrency with reference types
- Peter Alexander (26/26) Feb 04 2011 I would like to be able to spawn a thread, which does a lot of work, and...
- Jesse Phillips (11/16) Feb 04 2011 Disclaimer: I don't really do much with threading/concurrency.
- Peter Alexander (5/21) Feb 04 2011 Thanks, I'll take a look at parallelism.
- Sean Kelly (2/6) Feb 04 2011 What do you mean by blocking? The receive call will block until a messa...
- Peter Alexander (5/11) Feb 05 2011 That's what I meant by blocking (receive).
- spir (14/40) Feb 04 2011 (I have few exp in the paradigm, so don't believe me.)
- Jesse Phillips (2/11) Feb 04 2011 Well I think that this is the model that goroutines lend them selves to....
- Peter Alexander (13/65) Feb 04 2011 You may be right, I don't know (that's why I'm asking!)
- spir (8/13) Feb 04 2011 Yes, there was some times ago a thread about how to make template constr...
- Sean Kelly (2/9) Feb 04 2011 Could you provide an example? When passing reference data, the error yo...
- Peter Alexander (16/25) Feb 05 2011 Now that I've investigated a bit more, it appears to be unrelated to
- Denis Koroskin (10/40) Feb 05 2011 Because even though foo doesn't use any of the local variables (nor does...
- spir (15/43) Feb 05 2011 In complement to what Denis Koroskin answered: when a func is defined in...
I would like to be able to spawn a thread, which does a lot of work, and then returns (in some way) the result of its computations to the main thread. I would really like to avoid copying its results if possible. Logically, what I would like is something like this: class LotsOfData { ... } void doStuff(LotsOfData d) { ... } shared(LotsOfData[]) data; void main() { spawn( &doWork ); while(true) { foreach (d; data) doStuff(d); } } void doWork() { LotsOfData d = new LotsOfData; // lots of computation on d data ~= d; } Essentially, the work that doWork does needs to be returned to the main thread asynchronously, and obviously in a thread-safe manner. What's the best way to do this? The above won't work because ~= isn't atomic, so you can't do it on shared data.
Feb 04 2011
Peter Alexander Wrote:Essentially, the work that doWork does needs to be returned to the main thread asynchronously, and obviously in a thread-safe manner. What's the best way to do this? The above won't work because ~= isn't atomic, so you can't do it on shared data.Disclaimer: I don't really do much with threading/concurrency. You might look at using parallelfuture, the library to be added to Phobos as parallelism. An example similar to yours, though instead of the doWork getting a thread it is the doStuff function, and this example is for a call center: https://gist.github.com/774983 Call => LoadsOfData callGenerator => doWork Threed.sleep => doStuff You will probably also want to return an Immutable Call from callGenerator, that should result in a non-copy share (Though maybe it already does that). You may also want to look at std.concurrency and make use of message passing. https://gist.github.com/773979 Hopefully this are a good starting place.
Feb 04 2011
On 4/02/11 8:23 PM, Jesse Phillips wrote:Peter Alexander Wrote:Thanks, I'll take a look at parallelism. How would you do it with message passing though? As I understand, all of the std.concurrency message passing routines are blocking, and I need this to be asynchronous.Essentially, the work that doWork does needs to be returned to the main thread asynchronously, and obviously in a thread-safe manner. What's the best way to do this? The above won't work because ~= isn't atomic, so you can't do it on shared data.Disclaimer: I don't really do much with threading/concurrency. You might look at using parallelfuture, the library to be added to Phobos as parallelism. An example similar to yours, though instead of the doWork getting a thread it is the doStuff function, and this example is for a call center: https://gist.github.com/774983 Call => LoadsOfData callGenerator => doWork Threed.sleep => doStuff You will probably also want to return an Immutable Call from callGenerator, that should result in a non-copy share (Though maybe it already does that). You may also want to look at std.concurrency and make use of message passing. https://gist.github.com/773979 Hopefully this are a good starting place.
Feb 04 2011
Peter Alexander Wrote:How would you do it with message passing though? As I understand, all of the std.concurrency message passing routines are blocking, and I need this to be asynchronous.What do you mean by blocking? The receive call will block until a message matching one of the supplied types arrives, but if you don't like this you can always use receiveTimeout. send() doesn't deep copy objects, so the only reference types send() will currently accept are those labeled as shared or immutable (Unique!T will probably be added at some point, which is more appropriate for your situation). So to use send() known unique reference data you'll have to cast to/from shared or immutable. Nasty, but it'll work.
Feb 04 2011
On 4/02/11 11:44 PM, Sean Kelly wrote:Peter Alexander Wrote:That's what I meant by blocking (receive). Is using receiveTimeout with a timeout of 0 seconds the D-way of asynchronous message passing? (seems a bit hacky to me). Thanks for your reply.How would you do it with message passing though? As I understand, all of the std.concurrency message passing routines are blocking, and I need this to be asynchronous.What do you mean by blocking? The receive call will block until a message matching one of the supplied types arrives, but if you don't like this you can always use receiveTimeout. send() doesn't deep copy objects, so the only reference types send() will currently accept are those labeled as shared or immutable (Unique!T will probably be added at some point, which is more appropriate for your situation). So to use send() known unique reference data you'll have to cast to/from shared or immutable. Nasty, but it'll work.
Feb 05 2011
On 02/04/2011 07:18 PM, Peter Alexander wrote:I would like to be able to spawn a thread, which does a lot of work, and then returns (in some way) the result of its computations to the main thread. I would really like to avoid copying its results if possible. Logically, what I would like is something like this: class LotsOfData { ... } void doStuff(LotsOfData d) { ... } shared(LotsOfData[]) data; void main() { spawn( &doWork ); while(true) { foreach (d; data) doStuff(d); } } void doWork() { LotsOfData d = new LotsOfData; // lots of computation on d data ~= d; } Essentially, the work that doWork does needs to be returned to the main thread asynchronously, and obviously in a thread-safe manner. What's the best way to do this? The above won't work because ~= isn't atomic, so you can't do it on shared data.(I have few exp in the paradigm, so don't believe me.) It seems your problem is a typical case that cannot be safe as is. Essentially, IIUC, you want a shared set of data to be fed (more generally: mutated) from a thread, while another thread (here, the main one) processes bits of it. How can this be correct as is --except as you say if mutating operations were atomic? I think in such a case you /must/ have a communication protocal between both tasks/threads. It is not due to language features (present ot not, this or that way) but to the problem itself. Correct me if I'm wrong, please. Denis -- _________________ vita es estrany spir.wikidot.com
Feb 04 2011
spir Wrote:(I have few exp in the paradigm, so don't believe me.) It seems your problem is a typical case that cannot be safe as is. Essentially, IIUC, you want a shared set of data to be fed (more generally: mutated) from a thread, while another thread (here, the main one) processes bits of it. How can this be correct as is --except as you say if mutating operations were atomic? I think in such a case you /must/ have a communication protocal between both tasks/threads. It is not due to language features (present ot not, this or that way) but to the problem itself. Correct me if I'm wrong, please.Well I think that this is the model that goroutines lend them selves to. You have a producer input into a channel and a consumer on another thread/machine. To get such a behavior your don't need a language feature, but as of yet D does not have anything exactly comparable.
Feb 04 2011
On 4/02/11 8:42 PM, spir wrote:On 02/04/2011 07:18 PM, Peter Alexander wrote:You may be right, I don't know (that's why I'm asking!) I was hoping that I would be able to do something using synchronized classes, but I'm not fully up to speed with how they interact with shared and immutable, and all the semantics associated with it. I understand that passing immutable value types around is very easy, but unfortunately that's rarely practical. Things might be easier if the error messages associated with D's concurrent features weren't especially unhelpful (for example, trying to spawn a thread with reference type parameters just gives you a 'no match for spawn template' error). It's nice that it stops you from doing such things, but it would be nice if it told me why it's not going to let me do them.I would like to be able to spawn a thread, which does a lot of work, and then returns (in some way) the result of its computations to the main thread. I would really like to avoid copying its results if possible. Logically, what I would like is something like this: class LotsOfData { ... } void doStuff(LotsOfData d) { ... } shared(LotsOfData[]) data; void main() { spawn( &doWork ); while(true) { foreach (d; data) doStuff(d); } } void doWork() { LotsOfData d = new LotsOfData; // lots of computation on d data ~= d; } Essentially, the work that doWork does needs to be returned to the main thread asynchronously, and obviously in a thread-safe manner. What's the best way to do this? The above won't work because ~= isn't atomic, so you can't do it on shared data.(I have few exp in the paradigm, so don't believe me.) It seems your problem is a typical case that cannot be safe as is. Essentially, IIUC, you want a shared set of data to be fed (more generally: mutated) from a thread, while another thread (here, the main one) processes bits of it. How can this be correct as is --except as you say if mutating operations were atomic? I think in such a case you /must/ have a communication protocal between both tasks/threads. It is not due to language features (present ot not, this or that way) but to the problem itself. Correct me if I'm wrong, please. Denis
Feb 04 2011
On 02/05/2011 12:21 AM, Peter Alexander wrote:Things might be easier if the error messages associated with D's concurrent features weren't especially unhelpful (for example, trying to spawn a thread with reference type parameters just gives you a 'no match for spawn template' error). It's nice that it stops you from doing such things, but it would be nice if it told me why it's not going to let me do them.Yes, there was some times ago a thread about how to make template constraint errors more helpful. Denis -- _________________ vita es estrany spir.wikidot.com
Feb 04 2011
Peter Alexander Wrote:Things might be easier if the error messages associated with D's concurrent features weren't especially unhelpful (for example, trying to spawn a thread with reference type parameters just gives you a 'no match for spawn template' error). It's nice that it stops you from doing such things, but it would be nice if it told me why it's not going to let me do them.Could you provide an example? When passing reference data, the error you should see is: "Aliases to mutable thread-local data not allowed." It's a static assert inside send().
Feb 04 2011
On 5/02/11 12:11 AM, Sean Kelly wrote:Peter Alexander Wrote:Now that I've investigated a bit more, it appears to be unrelated to reference types, and instead was an error about using a nested function: import std.concurrency; void main() { void foo() {} spawn(&foo); } --- test.d(5): Error: template std.concurrency.spawn(T...) does not match any function template declaration test.d(5): Error: template std.concurrency.spawn(T...) cannot deduce template function from argument types !()(void delegate()) --- Why does it think that the function is a delegate?Things might be easier if the error messages associated with D's concurrent features weren't especially unhelpful (for example, trying to spawn a thread with reference type parameters just gives you a 'no match for spawn template' error). It's nice that it stops you from doing such things, but it would be nice if it told me why it's not going to let me do them.Could you provide an example? When passing reference data, the error you should see is: "Aliases to mutable thread-local data not allowed." It's a static assert inside send().
Feb 05 2011
On Sat, 05 Feb 2011 20:42:53 +0300, Peter Alexander <peter.alexander.au gmail.com> wrote:On 5/02/11 12:11 AM, Sean Kelly wrote:Because even though foo doesn't use any of the local variables (nor does main declare any), it still has frame pointer as if it was using some: void main() { int x = 42; void foo() { printf("%d", x); } spawn(&foo); }Peter Alexander Wrote:Now that I've investigated a bit more, it appears to be unrelated to reference types, and instead was an error about using a nested function: import std.concurrency; void main() { void foo() {} spawn(&foo); } --- test.d(5): Error: template std.concurrency.spawn(T...) does not match any function template declaration test.d(5): Error: template std.concurrency.spawn(T...) cannot deduce template function from argument types !()(void delegate()) --- Why does it think that the function is a delegate?Things might be easier if the error messages associated with D's concurrent features weren't especially unhelpful (for example, trying to spawn a thread with reference type parameters just gives you a 'no match for spawn template' error). It's nice that it stops you from doing such things, but it would be nice if it told me why it's not going to let me do them.Could you provide an example? When passing reference data, the error you should see is: "Aliases to mutable thread-local data not allowed." It's a static assert inside send().
Feb 05 2011
On 02/05/2011 06:42 PM, Peter Alexander wrote:On 5/02/11 12:11 AM, Sean Kelly wrote:In complement to what Denis Koroskin answered: when a func is defined inside another one, taking what looks like a func pointer to it automatically turns the result into a delegate. I also find this annoying, even more because there is no automagic func* <--> delegate cast. What I would like id: * No func* / delegate distinction on the user side (can be impl optimisation if significant). * Function auto-de/referencing: meaning in your code: spawn(foo). Denis -- _________________ vita es estrany spir.wikidot.comPeter Alexander Wrote:Now that I've investigated a bit more, it appears to be unrelated to reference types, and instead was an error about using a nested function: import std.concurrency; void main() { void foo() {} spawn(&foo); } --- test.d(5): Error: template std.concurrency.spawn(T...) does not match any function template declaration test.d(5): Error: template std.concurrency.spawn(T...) cannot deduce template function from argument types !()(void delegate()) --- Why does it think that the function is a delegate?Things might be easier if the error messages associated with D's concurrent features weren't especially unhelpful (for example, trying to spawn a thread with reference type parameters just gives you a 'no match for spawn template' error). It's nice that it stops you from doing such things, but it would be nice if it told me why it's not going to let me do them.Could you provide an example? When passing reference data, the error you should see is: "Aliases to mutable thread-local data not allowed." It's a static assert inside send().
Feb 05 2011