www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - std.concurrency and efficient returns

reply Jonathan M Davis <jmdavisprog gmail.com> writes:
Okay. From what I can tell, it seems to be a recurring pattern with threads
that 
it's useful to spawn a thread, have it do some work, and then have it return
the 
result and terminate. The appropriate way to do that seems to spawn the thread 
with the data that needs to be passed and then using send to send what would 
normally be the return value before the function (and therefore the spawned 
thread) terminates. I see 2 problems with this, both stemming from immutability.

1. _All_ of the arguments passed to spawn must be immutable. It's not that hard 
to be in a situation where you need to pass it arguments that the parent thread 
will never use, and it's highly probable that that data will have to be copied 
to make it immutable so that it can be passed. The result is that you're forced 
to make pointless copies. If you're passing a lot of data, that could be 
expensive.

2. _All_ of the arguments returned via send must be immutable. In the scenario 
that I'm describing here, the thread is going away after sending the message,
so 
there's no way that it's going to do anything with the data, and having to copy 
it to make it immutable (as will likely have to be done) can be highly 
inefficient.

Is there a better way to do this? Or if not, can one be created? It seems to me 
that it would be highly desirable to be able to pass mutable reference types 
between threads where the thread doing the receiving takes control of the 
object/array being passed. Due to D's threading model, a copy may still have to 
be done behind the scenes, but if you could pass mutable data across while 
passing ownership, you could have at most 1 copy rather than the 2 - 3 copies 
that would have to be taking place when you have a mutable obect that you're 
trying to send across threads (so, one copy to make it immutable, possibly a 
copy from one thread local storage to another of the immutable data (though I'd 
hope that that wouldn't require a copy), and one copy on the other end to get 
mutable data from the immutable data). As it stands, it seems painfully 
inefficient to me when you're passing anything other than small amounts of data 
across.

Also, this recurring pattern that I'm seeing makes me wonder if it would be 
advantageous to have an addititon to std.concurrency where you spawned a thread 
which returned a value when it was done (rather than having to use a send with
a 
void function), and the parent thread used a receive call of some kind to get 
the return value. Ideally, you could spawn a series of threads which were
paired 
with the variables that their return values would be assigned to, and you could 
do it all as one function call.

Overall, I really like D's threading model, but it seems to me that it could be 
streamlined a bit.

- Jonathan M Davis
Aug 01 2010
next sibling parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Sun, 01 Aug 2010 06:24:18 -0400, Jonathan M Davis  
<jmdavisprog gmail.com> wrote:
 Okay. From what I can tell, it seems to be a recurring pattern with  
 threads that
 it's useful to spawn a thread, have it do some work, and then have it  
 return the
 result and terminate. The appropriate way to do that seems to spawn the  
 thread
 with the data that needs to be passed and then using send to send what  
 would
 normally be the return value before the function (and therefore the  
 spawned
 thread) terminates. I see 2 problems with this, both stemming from  
 immutability.

 1. _All_ of the arguments passed to spawn must be immutable. It's not  
 that hard
 to be in a situation where you need to pass it arguments that the parent  
 thread
 will never use, and it's highly probable that that data will have to be  
 copied
 to make it immutable so that it can be passed. The result is that you're  
 forced
 to make pointless copies. If you're passing a lot of data, that could be
 expensive.

 2. _All_ of the arguments returned via send must be immutable. In the  
 scenario
 that I'm describing here, the thread is going away after sending the  
 message, so
 there's no way that it's going to do anything with the data, and having  
 to copy
 it to make it immutable (as will likely have to be done) can be highly
 inefficient.

 Is there a better way to do this? Or if not, can one be created? It  
 seems to me
 that it would be highly desirable to be able to pass mutable reference  
 types
 between threads where the thread doing the receiving takes control of the
 object/array being passed. Due to D's threading model, a copy may still  
 have to
 be done behind the scenes, but if you could pass mutable data across  
 while
 passing ownership, you could have at most 1 copy rather than the 2 - 3  
 copies
 that would have to be taking place when you have a mutable obect that  
 you're
 trying to send across threads (so, one copy to make it immutable,  
 possibly a
 copy from one thread local storage to another of the immutable data  
 (though I'd
 hope that that wouldn't require a copy), and one copy on the other end  
 to get
 mutable data from the immutable data). As it stands, it seems painfully
 inefficient to me when you're passing anything other than small amounts  
 of data
 across.

 Also, this recurring pattern that I'm seeing makes me wonder if it would  
 be
 advantageous to have an addititon to std.concurrency where you spawned a  
 thread
 which returned a value when it was done (rather than having to use a  
 send with a
 void function), and the parent thread used a receive call of some kind  
 to get
 the return value. Ideally, you could spawn a series of threads which  
 were paired
 with the variables that their return values would be assigned to, and  
 you could
 do it all as one function call.

 Overall, I really like D's threading model, but it seems to me that it  
 could be
 streamlined a bit.

 - Jonathan M Davis
Hi Jonathan, It sounds like what you really want is a task-based parallel programming library, as opposed to concurrent thread. I'd recommend Dave Simcha's parallelFuture library if you want to play around with this in D (http://www.dsource.org/projects/scrapple/browser/trunk/parallelFutur /parallelFuture.d). However, parallelFuture is currently unsafe - you need to make sure that logically speaking that data the task is being passed is immutable. Shared/const/immutable delegates have been brought up before as a way to formalize the implicit assumptions of libraries like parallelFuture, but nothing has come of it yet. As for std.concurrency, immutability is definitely the correct way to go, even if it means extra copying: for most jobs the processing should greatly out way the cost of copying and thread initialization (though under the hood thread pools should help with the latter). A large amount of experience dictates that shared mutable data, let alone unprotected mutable data, is a bug waiting to happen. On a more practical note, if you relaxing either 1) or 2) can cause major problems with certain modern GCs, so at a minimum casts should be involved.
Aug 01 2010
parent reply Jonathan M Davis <jmdavisprog gmail.com> writes:
On Sunday 01 August 2010 08:55:54 Robert Jacques wrote:
 Hi Jonathan,
 It sounds like what you really want is a task-based parallel programming
 library, as opposed to concurrent thread. I'd recommend Dave Simcha's
 parallelFuture library if you want to play around with this in D
 (http://www.dsource.org/projects/scrapple/browser/trunk/parallelFuture/para
 llelFuture.d). However, parallelFuture is currently unsafe - you need to
 make sure that logically speaking that data the task is being passed is
 immutable. Shared/const/immutable delegates have been brought up before as
 a way to formalize the implicit assumptions of libraries like
 parallelFuture, but nothing has come of it yet.
 As for std.concurrency, immutability is definitely the correct way to go,
 even if it means extra copying: for most jobs the processing should
 greatly out way the cost of copying and thread initialization (though
 under the hood thread pools should help with the latter). A large amount
 of experience dictates that shared mutable data, let alone unprotected
 mutable data, is a bug waiting to happen.
 On a more practical note, if you relaxing either 1) or 2) can cause major
 problems with certain modern GCs, so at a minimum casts should be involved.
I totally agree that for the most part, the message passing as-is is a very good idea. It's just that there are cases where it would be desirable to actually hand over data, so that the thread receiving the data owns it, and it doesn't exist anymore in the sending thread. I'm not sure that that's possible in the general case as nice as it would be. However, in my specific case - effectively returning data upon thread termination - I should think that it would be totally possible since the sending thread is terminating. That would require some extra functions in std.concurrency however rather than using receive() and send() as we have them. In any case, I'll have to look at Dave Simcha's parallelism library. Thanks for the info. - Jonathan M Davis
Aug 01 2010
next sibling parent Jonathan M Davis <jmdavisProg gmail.com> writes:
FWIW, I posted an enhancement request on the subject:

http://d.puremagic.com/issues/show_bug.cgi?id=4566

- Jonthan M Davis
Aug 01 2010
prev sibling parent "Robert Jacques" <sandford jhu.edu> writes:
On Sun, 01 Aug 2010 19:22:10 -0400, Jonathan M Davis  
<jmdavisprog gmail.com> wrote:

 On Sunday 01 August 2010 08:55:54 Robert Jacques wrote:
 Hi Jonathan,
 It sounds like what you really want is a task-based parallel programming
 library, as opposed to concurrent thread. I'd recommend Dave Simcha's
 parallelFuture library if you want to play around with this in D
 (http://www.dsource.org/projects/scrapple/browser/trunk/parallelFuture/para
 llelFuture.d). However, parallelFuture is currently unsafe - you need to
 make sure that logically speaking that data the task is being passed is
 immutable. Shared/const/immutable delegates have been brought up before  
 as
 a way to formalize the implicit assumptions of libraries like
 parallelFuture, but nothing has come of it yet.
 As for std.concurrency, immutability is definitely the correct way to  
 go,
 even if it means extra copying: for most jobs the processing should
 greatly out way the cost of copying and thread initialization (though
 under the hood thread pools should help with the latter). A large amount
 of experience dictates that shared mutable data, let alone unprotected
 mutable data, is a bug waiting to happen.
 On a more practical note, if you relaxing either 1) or 2) can cause  
 major
 problems with certain modern GCs, so at a minimum casts should be  
 involved.
I totally agree that for the most part, the message passing as-is is a very good idea. It's just that there are cases where it would be desirable to actually hand over data, so that the thread receiving the data owns it, and it doesn't exist anymore in the sending thread. I'm not sure that that's possible in the general case as nice as it would be.
Oh, sorry I missed that point. That has been seriously discussed before under the moniker of a 'unique'/'mobile' type. You might want to look up the dmd-concurrency mailing list or Bartoz's old blogs bartoszmilewski.wordpress.com. If I recall correctly, there was some plans to support the library unique struct in std.concurrency. However, Walter found that trying to fit it into the type system as a whole was too complex, so the concept is being left to the simpler library solution.
Aug 01 2010
prev sibling next sibling parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 Okay. From what I can tell, it seems to be a recurring pattern with threads
that
 it's useful to spawn a thread, have it do some work, and then have it return
the
 result and terminate. The appropriate way to do that seems to spawn the thread
 with the data that needs to be passed and then using send to send what would
 normally be the return value before the function (and therefore the spawned
 thread) terminates. I see 2 problems with this, both stemming from
immutability.
I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
Aug 01 2010
parent reply awishformore <awishformore nospam.plz> writes:
On 01/08/2010 19:17, dsimcha wrote:
 == Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 Okay. From what I can tell, it seems to be a recurring pattern with threads
that
 it's useful to spawn a thread, have it do some work, and then have it return
the
 result and terminate. The appropriate way to do that seems to spawn the thread
 with the data that needs to be passed and then using send to send what would
 normally be the return value before the function (and therefore the spawned
 thread) terminates. I see 2 problems with this, both stemming from
immutability.
I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
I completely agree with everything you said and I really dislike how D2 currently seems to virtually impose an application architecture based on the message passing model if you don't want to circumvent and thus break the entire type system. While I do agree that message passing makes a lot of sense as the default choice, there also has to be well thought-out and extensive support for the shared memory model if D2 is really focusing on the concurrency issue as much as it claims. Personally, I've found hybrid architectures where both models are combined as needed to be the most flexible and best performing approach and there is no way a language touted to be a systems language should impose one model over the other and stop the programmer from doing things the way he wants. /Max
Aug 01 2010
parent reply awishformore <awishformore nospam.plz> writes:
On 01/08/2010 21:25, awishformore wrote:
 On 01/08/2010 19:17, dsimcha wrote:
 == Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 Okay. From what I can tell, it seems to be a recurring pattern with
 threads that
 it's useful to spawn a thread, have it do some work, and then have it
 return the
 result and terminate. The appropriate way to do that seems to spawn
 the thread
 with the data that needs to be passed and then using send to send
 what would
 normally be the return value before the function (and therefore the
 spawned
 thread) terminates. I see 2 problems with this, both stemming from
 immutability.
I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
I completely agree with everything you said and I really dislike how D2 currently seems to virtually impose an application architecture based on the message passing model if you don't want to circumvent and thus break the entire type system. While I do agree that message passing makes a lot of sense as the default choice, there also has to be well thought-out and extensive support for the shared memory model if D2 is really focusing on the concurrency issue as much as it claims. Personally, I've found hybrid architectures where both models are combined as needed to be the most flexible and best performing approach and there is no way a language touted to be a systems language should impose one model over the other and stop the programmer from doing things the way he wants. /Max
P.S.: I find this to be especially true when taking into account the pragmatic approach under which D is supposed to be designed. D2 sounds a lot more idealistic than pragmatic, especially when it comes to concurrency, and I find that to be a very worrisome development. /Max
Aug 01 2010
parent reply Pelle <pelle.mansson gmail.com> writes:
On 08/01/2010 09:28 PM, awishformore wrote:
 On 01/08/2010 21:25, awishformore wrote:
 On 01/08/2010 19:17, dsimcha wrote:
 == Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 Okay. From what I can tell, it seems to be a recurring pattern with
 threads that
 it's useful to spawn a thread, have it do some work, and then have it
 return the
 result and terminate. The appropriate way to do that seems to spawn
 the thread
 with the data that needs to be passed and then using send to send
 what would
 normally be the return value before the function (and therefore the
 spawned
 thread) terminates. I see 2 problems with this, both stemming from
 immutability.
I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
I completely agree with everything you said and I really dislike how D2 currently seems to virtually impose an application architecture based on the message passing model if you don't want to circumvent and thus break the entire type system. While I do agree that message passing makes a lot of sense as the default choice, there also has to be well thought-out and extensive support for the shared memory model if D2 is really focusing on the concurrency issue as much as it claims. Personally, I've found hybrid architectures where both models are combined as needed to be the most flexible and best performing approach and there is no way a language touted to be a systems language should impose one model over the other and stop the programmer from doing things the way he wants. /Max
P.S.: I find this to be especially true when taking into account the pragmatic approach under which D is supposed to be designed. D2 sounds a lot more idealistic than pragmatic, especially when it comes to concurrency, and I find that to be a very worrisome development. /Max
import core.thread; You don't have to use the message passing interface if you don't want to.
Aug 01 2010
parent "Robert Jacques" <sandford jhu.edu> writes:
On Sun, 01 Aug 2010 16:02:43 -0400, Pelle <pelle.mansson gmail.com> wrote:
 On 08/01/2010 09:28 PM, awishformore wrote:

 import core.thread;

 You don't have to use the message passing interface if you don't want to.
Or use shared classes; you can pass those around too.
Aug 01 2010
prev sibling next sibling parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 Okay. From what I can tell, it seems to be a recurring pattern with threads
that
 it's useful to spawn a thread, have it do some work, and then have it return
the
 result and terminate. The appropriate way to do that seems to spawn the thread
 with the data that needs to be passed and then using send to send what would
 normally be the return value before the function (and therefore the spawned
 thread) terminates. I see 2 problems with this, both stemming from
immutability.
 1. _All_ of the arguments passed to spawn must be immutable. It's not that hard
 to be in a situation where you need to pass it arguments that the parent thread
 will never use, and it's highly probable that that data will have to be copied
 to make it immutable so that it can be passed. The result is that you're forced
 to make pointless copies. If you're passing a lot of data, that could be
 expensive.
 2. _All_ of the arguments returned via send must be immutable. In the scenario
 that I'm describing here, the thread is going away after sending the message,
so
 there's no way that it's going to do anything with the data, and having to copy
 it to make it immutable (as will likely have to be done) can be highly
 inefficient.
 Is there a better way to do this? Or if not, can one be created? It seems to me
 that it would be highly desirable to be able to pass mutable reference types
 between threads where the thread doing the receiving takes control of the
 object/array being passed. Due to D's threading model, a copy may still have to
 be done behind the scenes, but if you could pass mutable data across while
 passing ownership, you could have at most 1 copy rather than the 2 - 3 copies
 that would have to be taking place when you have a mutable obect that you're
 trying to send across threads (so, one copy to make it immutable, possibly a
 copy from one thread local storage to another of the immutable data (though I'd
 hope that that wouldn't require a copy), and one copy on the other end to get
 mutable data from the immutable data). As it stands, it seems painfully
 inefficient to me when you're passing anything other than small amounts of data
 across.
 Also, this recurring pattern that I'm seeing makes me wonder if it would be
 advantageous to have an addititon to std.concurrency where you spawned a thread
 which returned a value when it was done (rather than having to use a send with
a
 void function), and the parent thread used a receive call of some kind to get
 the return value. Ideally, you could spawn a series of threads which were
paired
 with the variables that their return values would be assigned to, and you could
 do it all as one function call.
 Overall, I really like D's threading model, but it seems to me that it could be
 streamlined a bit.
 - Jonathan M Davis
BTW, the other thing we need to make message passing viable even when small efficiencies don't count is an easy way of sending a deep clone of an arbitrarily complex object graph (clone would, I guess, return a Unique type) as a message to another thread. Of course there are some obvious limitations on what can be cloned. For example, raw pointers to data structures containing more pointer indirection couldn't be automagically deep cloned because you wouldn't know where the boundary between valid data and invalid data is. However, if I could just do something like: // myObj is unshared and is an arbitrarily complex object graph // that consists entirely of SafeD-style objects, not raw pointers. auto myObj = buildReallyComplicatedObject(); auto tid = spawn(&myFun, myObj.clone()); then I'd consider D's implementation of message passing viable for non-trivial use cases, as long as simplicity and safety matter more than performance and flexibility.
Aug 02 2010
prev sibling next sibling parent Sean Kelly <sean invisibleduck.org> writes:
Jonathan M Davis Wrote:

 Okay. From what I can tell, it seems to be a recurring pattern with threads
that 
 it's useful to spawn a thread, have it do some work, and then have it return
the 
 result and terminate. The appropriate way to do that seems to spawn the thread 
 with the data that needs to be passed and then using send to send what would 
 normally be the return value before the function (and therefore the spawned 
 thread) terminates. I see 2 problems with this, both stemming from
immutability.
 
 1. _All_ of the arguments passed to spawn must be immutable.
They must be shared. Immutable data is just implicitly shared.
 2. _All_ of the arguments returned via send must be immutable.
Same here.
 In the scenario 
 that I'm describing here, the thread is going away after sending the message,
so 
 there's no way that it's going to do anything with the data, and having to
copy 
 it to make it immutable (as will likely have to be done) can be highly 
 inefficient.
It sounds like what you really want is a thread pool. Maybe passing a shared delegate would work? It would be really nice to get unique sorted out though. Passing unique data via tid.send() should work too.
Aug 02 2010
prev sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
awishformore Wrote:
 
 I completely agree with everything you said and I really dislike how D2 
 currently seems to virtually impose an application architecture based on 
 the message passing model if you don't want to circumvent and thus break 
 the entire type system. While I do agree that message passing makes a 
 lot of sense as the default choice, there also has to be well 
 thought-out and extensive support for the shared memory model if D2 is 
 really focusing on the concurrency issue as much as it claims.
 
 Personally, I've found hybrid architectures where both models are 
 combined as needed to be the most flexible and best performing approach 
 and there is no way a language touted to be a systems language should 
 impose one model over the other and stop the programmer from doing 
 things the way he wants.
I wouldn't worry--the modules in core will always provide the low-level control that you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well.
Aug 02 2010
parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from Sean Kelly (sean invisibleduck.org)'s article
 I wouldn't worry--the modules in core will always provide the low-level control
that you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading? My personal opinion (which may change if I understand shared better) is that "here be dragons" is the way to go. Futures/tasks/parallel foreach are meant for parallelizing small hotspots, where the programmer generally knows implicitly what assumptions are being made, and generally revolve completely around shared memory and, where necessary, explicit locking. Forcing things to be cast to shared left, right and center would simply be an annoying piece of syntactic salt that doesn't actually solve any problems in these cases. (It would also completely destroy the nice, clean interface of the module I've created for task/future/parallel foreach.) I think Phobos's std.parallelism should just come with a big "here be dragons" sign and that's about it as far as safety/use of shared/etc.
Aug 03 2010
parent reply Sean Kelly <sean invisibleduck.org> writes:
dsimcha Wrote:

 == Quote from Sean Kelly (sean invisibleduck.org)'s article
 I wouldn't worry--the modules in core will always provide the low-level control
that you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading?
Ideally, the parallelism should be both invisible to the user and automatically safe. That isn't a realistic goal though, so a balance must be found between performance and safety. I'd venture to say the "here be dragons" approach is correct for the most part though. If too much performance is traded away for safety, no one will use it.
Aug 03 2010
next sibling parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Sean Kelly (sean invisibleduck.org)'s article
 dsimcha Wrote:
 == Quote from Sean Kelly (sean invisibleduck.org)'s article
 I wouldn't worry--the modules in core will always provide the low-level control
that you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading?
Ideally, the parallelism should be both invisible to the user and automatically
safe. That isn't a realistic goal though, so a balance must be found between performance and safety. I'd venture to say the "here be dragons" approach is correct for the most part though. If too much performance is traded away for safety, no one will use it. My other major concern is that if too much convenience (in simple cases where the programmer does know what he/she is doing even if the guarantees aren't statically checkable) is traded for safety (i.e. by requiring stuff to be cast explicitly to shared in lots of places), the API will become almost unusable.
Aug 03 2010
prev sibling parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Tue, 03 Aug 2010 16:05:40 -0400, Sean Kelly <sean invisibleduck.org>  
wrote:

 dsimcha Wrote:

 == Quote from Sean Kelly (sean invisibleduck.org)'s article
 I wouldn't worry--the modules in core will always provide the  
low-level control that you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading?
Ideally, the parallelism should be both invisible to the user and automatically safe. That isn't a realistic goal though, so a balance must be found between performance and safety. I'd venture to say the "here be dragons" approach is correct for the most part though. If too much performance is traded away for safety, no one will use it.
My experience with data-parallel programming leads me to believe that a large number of use cases could be covered by extending the D's set of function/member modifiers (i.e. const/shared/immutable/pure) to cover delegates. This would allow, for instance, a parallel foreach function to take a const delegate or a future function to take a shared delegate and thereby provide both safety and performance. Bartosz recently bloged about task driven parallelism in three "High Productivity Computing Systems" languages ( Chapel, X10, Fortress ) and criticized all three regarding taking the "here be dragons" approach. Even experts wake up the dragons on a semi-regular basis and debugging them back into slumber is both hard and time consuming; it's typically to see a 10x cost in development time on massively parallel architectures. For mere mortals to be productive, those dragons need to be slain. And I think the D has a decent chance to be the first(?) non-functional language to slay them. But I think this should be a goal of D3, and that a "here be dragons" library for D2 would be extremely useful: because if we're going to slay the dragons, we need to know as about many caves and dragons as possible.
Aug 03 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Robert Jacques:
 Bartosz recently bloged about  
 task driven parallelism in three "High Productivity Computing Systems"  
 languages ( Chapel, X10, Fortress ) and criticized all three regarding  
 taking the "here be dragons" approach. Even experts wake up the dragons on  
 a semi-regular basis and debugging them back into slumber is both hard and  
 time consuming;
D has to learn a large number of things/tricks from Chapel. I have written two posts to show some of them. Bye, bearophile
Aug 04 2010
prev sibling parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Robert Jacques (sandford jhu.edu)'s article
 My experience with data-parallel programming leads me to believe that a
 large number of use cases could be covered by extending the D's set of
 function/member modifiers (i.e. const/shared/immutable/pure) to cover
 delegates. This would allow, for instance, a parallel foreach function to
 take a const delegate or a future function to take a shared delegate and
 thereby provide both safety and performance. Bartosz recently bloged about
 task driven parallelism in three "High Productivity Computing Systems"
 languages ( Chapel, X10, Fortress ) and criticized all three regarding
 taking the "here be dragons" approach.
Given that Bartosz is a type system guru I can see where he's coming from. However, ironically we're talking about making such a library usable for mere mortals and I consider myself a mere mortal when it comes the complexities of type systems, in that I find Bartosz's posts extremely theoretical and difficult to follow. I actually find it easier to visualize how work can be interleaved between threads. Since shared is relatively new and (I think) not fully implemented, immutable is a good example of why not everything can be easily expressed in the type system. Immutable data has some wonderful theoretical properties, but creating immutable data in D without either sacrificing a significant amount of efficiency (via copying) or relying on unchecked casts, is close to impossible. Yes, we could have made unique a full-fledged type constructor, but that would have added another level of complexity to the language when const/immutable already seems complex to a lot of people. The result of this is that much data that I share across threads is logically immutable, but I've given up long ago on making most cases of this statically checkable. Also, with regard to the 10x development cost, I suspect a lot of that has to do with getting the code to scale, too. Code already becomes substantially harder to write, for example, when you can't freely heap allocate whenever you want (because you'd bottleneck on malloc and GC). Since we're talking about shared memory architectures here, I'll assume the cases you're referring to don't have thread-local heaps and that at least some synchronization points are required for memory management.
Aug 04 2010