[External] : Re: Meaning and Usage of Tasks
Eric Kolotyluk
eric at kolotyluk.net
Fri Nov 19 20:44:20 UTC 2021
Yes, I have been reading the JEP and understand things are very much still
in progress. Looking forward to seeing what develops
Do you mean Re-implement ThreadGroup JEP
<https://bugs.openjdk.java.net/browse/JDK-8252885>?
Cheeers, Eric
On Fri, Nov 19, 2021 at 12:39 PM Ron Pressler <ron.pressler at oracle.com>
wrote:
> If you read the structured concurrency JEP draft, you’ll see some sections
> that are still in progress that address the points you made. Stay tuned.
> Please try the new hierarchical JEP draft to see how some of those new
> APIs, which are still undocumented in the JEP, are used.
>
> — Ron
>
> On 19 Nov 2021, at 20:34, Eric Kolotyluk <eric at kolotyluk.net> wrote:
>
> For
>
> try (var structuredExecutor = StructuredExecutor.open("Experiment00", virtualThreadFactory)) {
> var completionHandler = new StructuredExecutor.ShutdownOnFailure();
> var futureResults = IntStream.range(0, 15).mapToObj(item -> {
> System.out.printf("item = %d, Thread ID = %s\n", item, Thread.currentThread());
> return structuredExecutor.fork(() -> {
> System.out.printf("\ttask = %d, Thread ID = %s\n", item, Thread.currentThread());
> return item;
> }, completionHandler);
> });
> }
>
> a satisfying experience in the future for me would be to see something
> like
>
> task = 0, Thread ID = Experiment00/VirtualThread[#15]@CarrierThread[#5]
> task = 1, Thread ID = Experiment00/VirtualThread[#22]@CarrierThread[#6]
> task = 2, Thread ID = Experiment00/VirtualThread[#28]@CarrierThread[#7]
> task = 3, Thread ID = Experiment00/VirtualThread[#30]@CarrierThread[#7]
>
> Some useful variations might be
>
> task = a, Thread ID = .../Experiment00/VirtualThread[#15]@CarrierThread[#5]
> task = b, Thread ID = .../Experiment00/VirtualThread[#22]@CarrierThread[#6]
> task = c, Thread ID = .../Experiment00/VirtualThread[#28]@CarrierThread[#7]
> task = d, Thread ID = .../Experiment00/VirtualThread[#30]@CarrierThread[#7]
>
> indicating that these threads have a parent, that can be * observed *through
> some other API, such as Thread.getParent()
>
> task = a, Thread ID = Experiment00/VirtualThread[#15]/... at CarrierThread[#5]
> task = b, Thread ID = Experiment00/VirtualThread[#22]/... at CarrierThread[#6]
> task = c, Thread ID = Experiment00/VirtualThread[#28]/... at CarrierThread[#7]
> task = d, Thread ID = Experiment00/VirtualThread[#30]/... at CarrierThread[#7]
>
> indicating that these threads have children, that can be * observed *through
> some other API, such as Thread.getChildren()
>
> While I initially imagined a fuller thread path, that could quickly
> produce unreadable signatures for .toString(), so the above is something
> compact but useful in terms of understanding the structure of Structured
> Concurrency at a glance.
>
> Of course, other people might have better ideas of a good thread signature
> for .toString().
>
> Cheers, Eric
>
> On Fri, Nov 19, 2021 at 10:31 AM Ron Pressler <ron.pressler at oracle.com>
> wrote:
>
>> It means that at the time toString was called, each of those two threads
>> were on that carrier (obviously, not at the same time). I’m not sure how
>> helpful this is, and there’s a good chance we’ll remove that string.
>>
>> — Ron
>>
>> On 19 Nov 2021, at 15:48, Eric Kolotyluk <eric at kolotyluk.net> wrote:
>>
>> " The toString method on a virtual thread will print the underlying
>> ForkJoinPool worker, but that’s not a new thread."
>>
>> Okay, that seems really profound. In my experiment, when I .fork() a
>> number of tasks, and each prints out Thread.currentThread(), I get
>> something like
>>
>> task = 0, Thread ID = VirtualThread[#15]/runnable at ForkJoinPool-1-worker-1
>> task = 1, Thread ID = VirtualThread[#22]/runnable at ForkJoinPool-1-worker-6
>> task = 2, Thread ID = VirtualThread[#28]/runnable at ForkJoinPool-1-worker-11
>> task = 3, Thread ID = VirtualThread[#30]/runnable at ForkJoinPool-1-worker-11
>>
>> What does this mean precisely?
>>
>> For the first one, I took this to mean VirtualThread[#15] is running in a
>> Carrier Thread called runnable at ForkJoinPool-1-worker-1
>>
>> For VirtualThread[#28] and VirtualThread[#30], these both seem to be
>> running in the same Carrier Thread runnable at ForkJoinPool-1-worker-1
>>
>> Am I interpreting this wrong? How should I interpret it?
>>
>> Cheers, Eric
>>
>>
>> On Fri, Nov 19, 2021 at 5:14 AM Ron Pressler <ron.pressler at oracle.com>
>> wrote:
>>
>>> It will create a new thread depending on the ThreadFactory. If the
>>> factory creates virtual threads, it will spawn a virtual thread per task;
>>> if it’s platform — platform. The toString method on a virtual thread will
>>> print the underlying ForkJoinPool worker, but that’s not a new thread.
>>>
>>> On 18 Nov 2021, at 21:44, Eric Kolotyluk <eric at kolotyluk.net> wrote:
>>>
>>> So, in the case of
>>>
>>> var platformThreadFactory = Thread.ofPlatform().factory();
>>>
>>> var structuredExecutor = StructuredExecutor.open("Experiment00", platformThreadFactory))
>>>
>>> You say "Because StructuredExecutor spawns a new thread for each forked
>>> task," it also creates a Platform Thread per task? Is this a property of
>>> .fork() or of StructuredExecutor?
>>>
>>> When I print out the Thread signature I see
>>>
>>> VirtualThread[#36]/runnable at ForkJoinPool-1-worker-11
>>>
>>> Which I find confusing, and I hope is improved in the future to be
>>> clearer in the relationship between threads and tasks.
>>>
>>> No, not an inconvenience at the moment, but I just want to understand
>>> better so that I can explain it to others, or ask better questions.
>>>
>>> Cheers, Eric
>>>
>>>
>>> On Thu, Nov 18, 2021 at 1:06 PM Ron Pressler <ron.pressler at oracle.com>
>>> wrote:
>>>
>>>> You bring up multiple points, so let me try and address them.
>>>>
>>>> A task is, indeed, not a thread, and, as you say, I would simply
>>>> consider it to be some computation, usually expressible as a lambda
>>>> expression/method reference. Because StructuredExecutor spawns a new thread
>>>> for each forked task, we might sometimes use the terms interchangeably, but
>>>> only in this context.
>>>>
>>>> As to the method names and signatures, the execute method is there
>>>> simply so that StructuredExecutor could implement the Executor interface
>>>> that declares it. You’ll find something similar in ExecutorService, where
>>>> there’s a method called executor, that takes a Runnable and returns void,
>>>> and methods called submit, which take a Callable and return a Future.
>>>> StructuredExecutor.fork is analogous to ExecutorService.submit. Now,
>>>> ExecutorService also has an override of submit that takes a Runnable (and
>>>> returns a Future), in addition to the void execute method. We could
>>>> certainly add a similar override of fork, but it didn’t seem particularly
>>>> urgent at this point in time. You can always turn a Runnable into a
>>>> Callable with the Executors.callable [1] helper method. If you find this to
>>>> be too much of an inconvenience, please let us know.
>>>>
>>>> — Ron
>>>>
>>>> [1]:
>>>> https://download.java.net/java/early_access/loom/docs/api/java.base/java/util/concurrent/Executors.html#callable(java.lang.Runnable)
>>>> <https://urldefense.com/v3/__https://download.java.net/java/early_access/loom/docs/api/java.base/java/util/concurrent/Executors.html*callable(java.lang.Runnable)__;Iw!!ACWV5N9M2RV99hQ!YbndcNiuJTwahpzmlh8-yXOmcGk0pp-R9VHjQpyMByNdAMV83lJfX4WrSLUjhBHNJQ$>
>>>>
>>>> > On 18 Nov 2021, at 20:18, Eric Kolotyluk <eric at kolotyluk.net> wrote:
>>>> >
>>>> > Reading some of the documentation I am getting confused about the
>>>> meaning
>>>> > of a 'Task' and wrote about this earlier in Threads vs Tasks...
>>>> >
>>>> > Initially, these were just Runnable, and then could also be Callable
>>>> (which
>>>> > is better). Is it interesting to note in StructuredExecutor there are
>>>> > different signatures for
>>>> >
>>>> > .execute(Runnable)
>>>> > .fork(Callable)
>>>> >
>>>> > And I appreciate the distinction because calling .submit(Runnable |
>>>> > Callable) can blur things, but can also see that .submit() may be more
>>>> > attractive to some. Also, Callable can throw a Checked Exception,
>>>> whereas
>>>> > Runnable cannot. Can someone please explain the reason for this design
>>>> > change?
>>>> >
>>>> > I especially appreciate that both Runnable and Callable can be
>>>> expressed
>>>> > with Lambda expressions, especially coming from a background of
>>>> Akka/Scala,
>>>> > where Actors .spawn() other Actors, and can use Lambda expressions.
>>>> > However, in Akka Typed we often use forms such as
>>>> >
>>>> > object GreeterMain {
>>>> >
>>>> > final case class SayHello(name: String)
>>>> >
>>>> > def apply(): Behavior[SayHello] =
>>>> > Behaviors.setup { context =>
>>>> > val greeter = context.spawn(Greeter(), "greeter")
>>>> >
>>>> > Behaviors.receiveMessage { message =>
>>>> > val replyTo = context.spawn(GreeterBot(max = 3), message.name
>>>> <https://urldefense.com/v3/__http://message.name__;!!ACWV5N9M2RV99hQ!YbndcNiuJTwahpzmlh8-yXOmcGk0pp-R9VHjQpyMByNdAMV83lJfX4WrSLUHGEwEqw$>
>>>> )
>>>> > greeter ! Greeter.Greet(message.name
>>>> <https://urldefense.com/v3/__http://message.name__;!!ACWV5N9M2RV99hQ!YbndcNiuJTwahpzmlh8-yXOmcGk0pp-R9VHjQpyMByNdAMV83lJfX4WrSLUHGEwEqw$>,
>>>> replyTo)
>>>> > Behaviors.same
>>>> > }
>>>> > }}
>>>> >
>>>> > where the Behaviors.setup { context => ... is a Lambda that takes a
>>>> > 'context'
>>>> >
>>>> > Now I am not advocating adopting the Akka style, but it makes me
>>>> wonder
>>>> > what it would look like if we had capabilities like
>>>> >
>>>> > structuredExecutor.spawn( () => {task implementation} )
>>>> > structuredExecutor.spawn( context => {task implementation} )
>>>> > structuredExecutor.spawn( (context, thingy) => {task implementation} )
>>>> > . . .
>>>> >
>>>> > and so on, where context is something not available in the session
>>>> because
>>>> > the structuredExecutor is available to the task implementation. I am
>>>> just
>>>> > thinking out loud here, where not all Tasks need the context, thingy,
>>>> etc.,
>>>> > but some may benefit from them. More importantly, is there some
>>>> concept of
>>>> > Task that goes beyond Callable, that cannot be satisfied by adding
>>>> more
>>>> > features to Callable?
>>>> >
>>>> > I am pretty sure the Loom Architects have already walked down this
>>>> path so
>>>> > I would like to know if they could share their thoughts on this.
>>>> >
>>>> > I am not hung up on a single .spawn() for multiple task types, but I
>>>> like
>>>> > the name because it brings a sense of 'family' to concurrent
>>>> programming. ��
>>>> >
>>>> > In my mind, a Task is *not *a Thread, it is something else that can be
>>>> > executed on a Thread, but it should always be implementable by a
>>>> Lambda.
>>>> >
>>>> > Cheers, Eric
>>>>
>>>>
>>>
>>
>
More information about the loom-dev
mailing list