http://www.perlmonks.org?node_id=866090


in reply to Re^5: Why Coro?
in thread Why Coro?

Well... defining what a real thread is kind of confusing.

Actually, it's not. A "thread" is a schedulable unit of execution context. Thereby making kernel threads like Windows threads and pthreads--as used by ithreads--real threads. (The 'real' is redundant.)

It also makes some user-space implementations--such as found in Java 1.1, Erlang, and others--that implement their own internal scheduler, also threads.

But coroutines are not threads. They are coroutines.

I think Coro is really neat

I also think Coro is extremely clever code. And its author, an extremely clever coder. There have even been a few occasions when I have sorely wished that Coro ran on my platform. There is no reason it shouldn't. The basic, underlying longjump mechanism works natively just fine--it is used for exception handling. It's just the implementation that prevents it.

And my recognition of the author's skills and knowledge are what makes me think that his diatribe in the Coro POD, is neither ignorance nor confusion> But simple politicking of the worst kind. Done in the full knowledge and aforethought of malice, that it is both factually incorrect, and likely to lead some--like binary perhaps--into confusion.

I think that if there is any real confusion, it comes because Linux treats threads and processes very similarly. To the extent that some versions of top actually list the threads of a single process as if they were separate processes.

To quote

Threads of execution, often shortened to threads, are the objects of activity within the process. Each thread includes a unique program counter, process stack, and set of processor registers. The kernel schedules individual threads, not processes. In traditional Unix systems, each process consists of one thread. In modern systems, however, multithreaded programs—those that consist of more than one thread—are common. As you will see later, Linux has a unique implementation of threads: It does not differentiate between threads and processes. To Linux, a thread is just a special kind of process.

The thing that makes them "special", is that they share address space. Perl's threads also share address space at the C level.

It is the programming model that ithreads layers on top of those underlying kernel threads, that restricts the access of individual threads within the process, to subsets of the full memory allocated to that process.

It does this by segregating memory allocations made by different threads, to different segments ("arenas") of the memory allocated to the process. But it is only Perl and the threading model chosen, that enforces this segregation; not the OS. Indeed, the segregation is quite easily defeated.

The choice of an 'explicitly-shared only' model was a) a concious choice; b) done with very good reason.

And IMO c) will in the longer term be seen as both inspired, and "the way to go".

The current implementation lets it down somewhat because of its memory-hungriness, and (lack of) speed. But this could (and hopefully, soon will be) addressed. The main problem with the current implementation is that is uses a 'double-tieing' mechanism for the scalars held in shared aggregate structures.

That is to say, both the AV or HV of a shared structure, and the individual scalars they contain, have attached magic. This means that not only is the size of every aggregate-held scalar, inflated in size by the attached magic, but also that each thread that has visibility of the shared structure, also requires a--relatively lightweight, but still significant--place-holder or alias object to every scalar held in the shared structure. This is both quite costly--and unnecessary.

The scalars that live within a tied aggregate don't need to have individually attached magic. (Nor even any physical storage allocation, but that's a twist that we can skip for now.) When a FETCH or STORE is invoked upon a tied array ot hash, the magic attached to the AV or HV has enough information to read or write the actual element without requiring further magic be attached to each individual scalar.

Not only would the removal (or rather the avoidance of attachment) of magic to the individual scalars considerably lessen the size of the shared aggregates, it would also remove the need for per-thread place-holders for them also. So, each thread would retain a single, lightweight reference to the shared AV or HV, and access it contents through that via it's attached magic, with the result that the memory cost of the shared aggregate is further reduced.

The final icing on the cake is that indirecting through only one level of magic instead of two would considerably speed up accesses.

In a nutshell, you can wrap a class around an aggregate with having to make the individual elements of that aggregate objects in their own right. And the memory and performance saving of that are legion. And this could (and will if I ever master the intricacies of XS) be implemented now.

But none of this detracts from either the desirability of preventing the unintentional, accidental sharing of thread-specific data; nor the usability of the current implementation. Just as with regexes (and every other aspect of Perl, and other languages), implementations can be improved, incrementally over time. Provided that the basic programming model is right.

And (IMO) the ithreads model is.


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

Replies are listed 'Best First'.
Re^7: Why Coro?
by juster (Friar) on Oct 19, 2010 at 18:20 UTC
    Actually, it's not. A "thread" is a schedulable unit of execution context. Thereby making kernel threads like Windows threads and pthreads--as used by ithreads--real threads. (The 'real' is redundant.) It also makes some user-space implementations--such as found in Java 1.1, Erlang, and others--that implement their own internal scheduler, also threads. But coroutines are not threads. They are coroutines.

    Coro implements its own scheduler. By this definition a "coro" created with Coro's "async" call is a "thread". It contains its own execution context which can be suspended and resumed. I still do not understand why coros created by Coro are not user-space threads in your terminology?

    Coro has much more functionality than the classical definition of the coroutine. To implement a coroutine you allow multiple exit points of the coroutine for yielding back to the caller. This is very simple compared to the full functionality of Coro which may be better described as a system of fibers.

    The only distinction between coroutines and fibers seem to be that coroutines are built into the language itself and are much simpler. All it takes to implement coroutines is a yield keyword that stops execution, returns a value, and resumes execution after the yield. Coro now seems less like a coroutine because it cannot cede (Coro's yield) any values to its caller. Coro also implements more sophisticated operations than the traditional coroutine. It seems to me more like fibers. More hair splitting... sigh.

    Fibers multitask cooperatively. User-level threads use user-level scheduling, which may may very well be cooperative multi-tasking... confusing! GNU Portable Threads (ironic misnomer?) is an example user-level thread library that uses cooperative multi-tasking and switches context instead of I/O blocking. Yes it has threads in its name... argh. This implementation also seems similar to Coro which uses cooperative multi-tasking and yields control instead of blocking on I/O. One difference is that Portable Threads can also schedule its "threads" based on priority.

    Are fibers threads? The wikipedia entry starts off with:

    In computer science, a fiber is a particularly lightweight thread of execution.

    Sheesh... I can see you are adamant in your terminology but my task of translating between everyone's disparate definition of what a "thread" really is, is confusing.

    After a quick skim of the abstract confusions, enter the tangible! The fact that perl's ithreads have created a bunch of little pseudo-processes out of the threads! I'm not sure I'm convinced how forward-thinking it is to model processes by using threads. "Real" (OS-level) Processes already must explicitly share data. Processes have private data. Processes run their own interpreter. Process run in parallel. Why reinvent the wheel?

    Add in the escher-esque quality of starting an OS process to run perl, which creates a main OS/kernel thread... now perl's ithreads creates more OS/kernel threads which in turn act like separate perl processes. ithreads have their own interpreter, they share data through complex operations akin to IPC which means they are as fast as processes sharing data. This seems more like going backwards than forwards.

    This is what Coro's author is apparently talking about in his short description: "Coro - the only real threads in perl". So what is the "real" thread? In my own mind they are both "real" threads with Coro leaning towards a fiber and perl ithreads leaning towards a process.

    Your quote on linux threads is a good example of threads in a specific context. Namely the linux operating system and its process model. I was trying to nail down the definition of thread in a wider, abstract context. Namely all of computer science. Just about the only decent source I could find was wikipedia.

    The rest of your post about aggregator magic was very interesting!

      First. Before entering the fray; thank you for your reasoned responses.

      I do understand your attempts to reconcile disparate interpretations of terms. But, if the world and his dog are allowed to misappropriate terminology to mean whatever they decide it should mean, then we'll end up with having to qualify every use; (" ... threads(Lehman'2007) ... threads(Wikipedia'1995) ... threads(SomoneElse'someOtherTime) ...". I hope every one can see how nonsensical that would become.

      For a pretty definitive history of both the term and the concept, seeThe history of threads.

      And if you read it all carefully you arrive at the widely accepted definition: In computer science, a thread of execution is the smallest unit of processing that can be scheduled by an operating system.

      I am fully aware that the last four words of that sentence contradicts my acceptance of the term threads to describe the concurrency in Erlang despite its operating in user-space. But I believe that is justifiable, at least since 2006, and I'll attempt to do that by contrasting its properties with those of Coro.

      Coro implements its own scheduler. ... I still do not understand why coros created by Coro are not user-space threads in your terminology?

      Cooperative scheduling is an oxymoron. Like cooperative management or cooperative teenager :)

      Coro may call its dispatcher, a "scheduler", but it's Just Another Misnomer™. To put the term 'scheduler' into context, think about other things that keep schedules. Neither trains, buses nor planes wait for their passengers to turn up. On the other hand, taxis are dispatched at the passenger's convenience.

      The distinction that sets Erlang's user-space scheduler apart is that (since 2006), a) it scales across SMP cores; b) if one context of execution (CoE) hangs, it doesn't stop others from progressing.

      Coro can do neither of these things. Hence why Coro's coros don't qualify as threads.

      Are fibers threads?

      No. Despite that they may start out as threads. The distinction is that they are not scheduled. A group of fibres may cooperate within the auspices of a single kernel thread, that is itself scheduled. In this way, fibres are distinct and more powerful than coroutines because they a) can scale across SMP cores; b) can coexist and run concurrently with other threads (which in turn may actually be groups of fibres). But still, as with coroutines, if one stops cooperating, the whole group hangs.

      Coros can't do this either, so they aren't fibres.

      (Though if they would coexist with ithreads, they could become fibres--which would be eminantly useful!)

      The fact that perl's ithreads have created a bunch of little pseudo-processes out of the threads!

      This is a misinterpretation. Pseudo-forks are created (on win32 only), using the fork keyword. They are nothing to do with the threads module, or the async() function or new() or create() methods of that module. You don't even need to use threads in order to use the pseudo-fork emulation. You just use the fork keyword as on any other platform.

      At the C (perl's internals) level, and the OS level, all ithread instances share a single address space. Ithreads are threads, in every sense of that term. They are not processes. They are not pseudo-processes. If you print out $$ from all the threads in a process, you get the same process id. If you kill that process id, you kill all the threads it contains.

      I'm not sure I'm convinced how forward-thinking it is to model processes by using threads. "Real" (OS-level) Processes already must explicitly share data. Processes have private data. Processes run their own interpreter. Process run in parallel. Why reinvent the wheel?

      Actually, when it comes to pseudo-forks, I agree with you. It was done for one reason only. A (as time has proven, somewhat misbegotten) attempt to make *nix programming paradigms (fork & exec etc.) more easily portable to non-*nix platforms. (Predominantly windows, but also OS/2, and possibly others).

      The motivations are good, and the implementation sort of works. But to be properly successful, so many other *nix concepts that are not supported on those other platforms--signals, unix-domain sockets, all-IO-is-files etc.--either cannot, or cannot adequately be emulated, that provision of fork and a very limited signalling capability, do not really help much in porting. They simply move the portability issues elsewhere, whilst adding a whole new bunch of caveats and special cases to be considered.

      In the end, it is generally simpler to ignore pseudo-forks and move directly to ithreads which are pretty consistent across all platforms.

      But, I suspect that when you said:

      I'm not sure I'm convinced how forward-thinking it is to model processes by using threads.

      You weren't referring to the little-known and less used pseudo-forks. But were actually talking about the ithreads explicitly-shared-only data model--and likening it to "modelling processes".

      When I first came to perl (5.6.1), ithreads were there already. The 5005-threads were also there.

      My platform doesn't have fork, and I came from a background that meant that kernel threads had been a part of my daily programming life for 15 or 20 years. So using threads was the most natural thing in the world to me.

      When I first read the distinction between the two threading models, 5005-threads everything shared model was far more like I was used to from C/C++/assembler etc. I was never in a position to have any input to the transition from 5005-threads to ithreads, but I remember being somewhat pissed at the prospect of the change.

      However, as time has gone on, despite all the limitations of the current implementation, I've come to view the ithreads model as inspired. I'll try to explain why.

      Historically, and continuing, the biggest percieved problem with threading--and all forms of shared state concurrency including coroutines--is the accidental sharing of state. It is pernicious because most times it doesn't cause crashes. Just wrong results that are a bitch to track down.

      The 'natural' reaction to this problem is to lock everything in sight. But this cure is worse than the disease. It leads to the other head-liners of the anti-shared-state lobby: deadlocking and priority inversions.

      Been there, done that, worn out the t-shirts. But here's the thing. In the nearly 10 years I've been programming ithreads, I've never once had a deadlock or a priority inversion. And on the very few occasions I've encountered shared state corruption, it has been the work of seconds to track down and correct.

      The reason is that very little state is shared, and when it is, it is explicitly marked as shared.

      That simple expedient of having the api/threading model require you to mark shared state with :shared--so simple to do, and yet so powerful in what it achieves in terms of its proactive, prophylactic properties--is (IMO) simply the most useful, usable, powerful addition to the concurrency programmers tool-kit ever. I mean it. Bar none.

      It's better than const'ing, immutability, message-passing, and software transactional memory (STM). Individually or all combined. (Note: I'm talking about the ithread concept rather than the current implementation!)

      Why? Because it mirrors the way people think. It allows them to intuit about what is shared and what is not. Without adding the duplication costs of immutability; or those plus the paradigm shift of message passing; nor the horrendous uncertainties, increased non-determinisms, and hyper-geometric worst-cases of STM.

      The application programmers view of a thread is that it is simply a non-blocking subroutine. It (usually) has a name; some parameters; some local variables, some code that processes those parameters; and a result. (And cleverly, in perl's case, it also has read-only(*) access to outer scopes--closures. Though this may not always be a good thing.)

      A view so simple that it takes no effort to intuit about it. It is simply a piece of work that can be delegated to someone else with: "Do this and don't bother me. I'll ask you for the results when I'm ready".

      they share data through complex operations akin to IPC

      This is a limitation of the implementation, not the concept. It could be improved now as I explained in my previous post.

      It could also be improved further than that, in a way that would make it even more intuitive. And such that the performance impact of the thread-enabling of the perl core, on non-threaded code could be severally reduced, if not entirely reversed. I've discussed this at length before and won't reiterate it all here, but in brief. Shared data is, intuitively, global data. And if only global (non-my) data could be shared, then all the internal locking that currently must be applied to my variables--even in non-threaded code--would become unnecessary. Win-win.

      So yes. I am quite adamant about my use of terminology--on this subject and many others--because I simply don't think it does anyone any favours at all to conflate terminology. It just confuses and deceives and serves no good purpose. As a UK comedian recently had it: "Describing scourers as "multi-purpose" is simply dishonest. Sure you can scrub many different things with them. But it's still eff'ing scrubbin'!"

      Coro; the name says it all. Coroutines are a useful, powerful concept in their own right. But they are not--by any stretch of the imagination--threads. Much less "the only Real threads".

      But more to the point. They don't need to advertise themselves as threads. Indeed, if they worked on my platform (and they could), and especially if the worked in conjunction with threads (which they could), then I'd be both using them and promoting them, and thanking the author for providing them.

      But, whilst the author continues to devote his POD to making fatuous claims, and enshrining his technical disputes with other respected members of the Perl community, rather than describing it functionality, I'll have no compunction about calling him on it.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
      After a quick skim of the abstract confusions, enter the tangible! The fact that perl's ithreads have created a bunch of little pseudo-processes out of the threads! I'm not sure I'm convinced how forward-thinking it is to model processes by using threads. "Real" (OS-level) Processes already must explicitly share data. Processes have private data. Processes run their own interpreter. Process run in parallel. Why reinvent the wheel? Add in the escher-esque quality of starting an OS process to run perl, which creates a main OS/kernel thread... now perl's ithreads creates more OS/kernel threads which in turn act like separate perl processes. ithreads have their own interpreter, they share data through complex operations akin to IPC which means they are as fast as processes sharing data. This seems more like going backwards than forwards.

      Yes, indeed! You're spot on here. This is exactly what ithreads are and it's what perlthrtut means when it says:

      In this model, each thread runs in its own Perl interpreter, and any data sharing between threads must be explicit. The user-level interface for ithreads uses the threads class.

      That line tells us everything we need to know and it's why I quoted it in my above post. You've just elucidated what the documentation has said in that line in a much clearer and more explicit way.

      "its own Perl interpreter" -- That tells us it's not really a thread, but a separate process as a real thread isn't a complete copy with its own interpreter but a unit of execution inside a process that shares address space with other unique units of execution.

      "data sharing between threads must be explicit" -- And finally this tells us that they don't share address space and if you want shared data you must do it by some other means just as you would between any other 2 separate processes. Ie the standard way with ithreads is using the complex operations as you put it, ie IPC.

      Also, again, please don't respond to me Browser. This response is not aimed at you, but for Juster and the general community. I've heard all you've had to say on the matter and I respectfully disagree, so please refrain from attacks on me.

        "its own Perl interpreter" -- That tells us it's not really a thread, but a separate process

        Each iThread, is simply another instance of the Perl interpreter running in it's own kernel thread within that same process.

        It is therefore not a "a separate process" in any way shape or form.

        "data sharing between threads must be explicit" -- And finally this tells us that they don't share address space

        All threads, including iThreads share the same, single address space of the parent process. The iThreads model ensures that only data explicitly marked as shared can be accessed from multiple threads. This mechanism protects the programmer from accidental shared-state corruptions. This is the same as the way Perl protects the programmer from "pointer problems" by not giving him direct access to machine level pointers. With the same benefits.

        so please refrain from attacks on me.

        This is not an attack on you, but rather, (as always), a correction of misinformation you are disseminating.

        As long as you feel the need to continue to do to disseminate such misinformation, I'll feel free to correct it.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        A reply falls below the community's threshold of quality. You may see it by logging in.