Concurrency Is Not Parallelism (Rob pike)

Rob pike发表过一个有名的演讲《Concurrency is not parallelism》(https://blog.golang.org/concurrency-is-not-parallelism),

演讲胶片在talks.golang.org中可以找到(https://talks.golang.org/2012/waza.slide#1),

演讲视频地址 :https://vimeo.com/49718712

以下是根据视频转换的文本信息。

if you looked at the programming
languages of today you probably get this
idea that the world is object oriented
but it's not it's actually parallel
you've got everything from the lowest
level like multi-core machines and up
through networking and so on somebody
gets all the way up to users who knows
planets the universe there's all these
things that are happening simultaneously
in the world and yet the computing tools
that we have are really not very good at
expressing that kind of worldview and
that seems like a failing but we can fix
that if we understand what concurrency
is and how to use it now I'll assume
that most of you at least heard of the
go programming language is just what
I've been working on at Google the last
few years and go is a concurrent
language which means that it has some
things that make concurrency useful like
is God the ability to execute things can
currently the ability to communicate
between things that are executing
concurrently and it's got this thing
called a select statement which is a
multi-way concurrent control switch if
that doesn't make any sense to you yet
don't worry but when we announced go
which was about two years ago all these
programmers out there said Oh concurrent
tools I know what to do like and run
stuff in parallel yay but that actually
isn't true concurrency and parallelism
are not the same thing it's a commonly
misunderstood problem and I'm here to
try to explain why and to show you that
concurrency is actually better what
would happen to these people who were
confused was they take a program they'd
run it on more processors and it would
get slower and they think this is broken
it doesn't work I'm going away but what
was really broken was the world view and
I hope I can fix that so what is
concurrency well concurrency as I'm
using it as its intended to be used in
the computer science world is a way to
build things it's the concurrent it's a
composition of independently executing
things typically functions but they
don't have to be and we usually express
those as the is interacting processes
now by process I don't mean a Linux
process I mean the sort of general
concept that embodies threads and co
routines and proceeds the whole thing so
I think in the most abstract possible
sense so it's the composition of
independently executing processes
parallel ism on the other hand is the
simultaneous
Dainius execution of multiple things
possibly related possibly not and if you
think about it in sort of a general
hand-wavy way concurrency is about
dealing with a lot of things at once and
parallelism is about doing a lot of
things at once and those are obviously
related but they're actually separate
ideas and there's a little confusing to
try to think about them if you don't
have the right tool kit so one is really
about structure concurrency and one is
about execution parallelism and I'll
show you why those are important so
concurrency is a way to structure a
thing so that you can maybe use
parallelism to do a better job but
parallel ism is not the goal of
concurrency concurrence ease goal is a
good structure so here's an analogy
might be familiar with if you're running
an operating system it might have a
mouse driver a keyboard driver display
driver network drivers whatever else and
those are all managed by the operating
system as independent things inside the
colonel but those are concurrent things
they aren't necessarily parallel if you
only one processor only one of them is
ever running at a time and so there's a
concurrent model for these in bio
devices but it's not inherently parallel
it doesn't need to be parallel whereas a
parallel thing might be something like a
vector dot product which you can break
that into microscopic operations as you
can execute on some fancy computer in
parallel very different idea not the
same thing at all so in order making
currency work though you have to add
this idea of communication which I'm not
going to focus on too much today but
you'll see a little bit about it so
concurrency gives you a way to structure
programming to independent pieces but
then you have to coordinate those pieces
and to make that work you need some form
of communication and tony hoare in 1978
wrote a paper called communicating
sequential processes which is truly one
of the greatest papers in computer
science and if you haven't read it if
anything out of this talk sinks in is
that you should go home and read that
paper it's absolutely amazing but based
on that a lot of people with lesser
minds have followed and built tools to
use these his ideas into concurrent
languages like Erlang is another one
that's great go has some of these ideas
in it but the key points are all in that
original paper with with a couple of
minor exceptions which will come up to
but look this is all way too abstract we
need Gophers
so let's get some Gophers going here's a
real problem we want to solve ok we have
a pile of ancient of obsolete manuals
may say the C++ 98 manuals know that C++
11 is 0 or maybe it's the C++ 11 books
we don't need them anymore whatever the
point is we got to get rid of them
they're taking up space so we have a go
for whose job is to take the books from
one pile and move them into the
incinerator and get rid of them okay but
with only one go over it's going to take
a long time if it's a big pile also
Gophers not very good at moving books
although we've given in the cart so
let's put another gopher in the problem
except he's not going to get any better
right because he needs the tools and
this this is kind of pointless we need
to give him all the parts he needs in
order to do this so this gopher needs
not only the ability to be a gopher but
he also needs the tools to get the job
done so let's give them another car now
that's going to go faster we're
definitely going to be able to move
books quicker with two Gophers pushing
the cards but of course there may be a
little problem because we're going to
have to synchronize them they can get
stuck at the incinerator the book pile
getting each other's way running back
and forth so they're going to need to
coordinate a little bit so you can
imagine the Gophers sending for the
little tony hoare messages saying Here I
am I need space to put the books in the
incinerator or whatever it is but you
get the idea this is silly but I want to
make it really clear these ideas are not
deep so just they're just good okay well
how do we make them go faster well we
double everything we put two Gophers in
and we double the the pile that the
piles and the incinerators as well as
the Gophers and now we can move twice as
many books in the same amount of time
that's parallel right but think of it
instead of parallel as really the
concurrent composition of to go for
procedures moving books so concurrency
is how we've expressed the problem these
two this this gopher guy can do this and
we paralyze it by instantiating more
instances of this gopher procedure and
that's called the concurrent composition
of processes or in this case Gophers now
this design is not automatically
parallel because sure there's two
Gophers but who says they both have to
work at the same time I could say the
only one gopher is allowed to move at
once which would be like having a unit a
single core computer and the design is
still concurrent and correct a nice but
it's not intrinsically
parallel and thus i can make both those
Gophers move at once that's when the
parallelism comes in having two things
executing simultaneously not just having
two things okay that's a really
important model but once we've decided
that we understand we can break the
problem down under these concurrent
pieces we can come up with other models
so here's a different design okay now we
got three golfers on in them in the
picture it's the same pile of books the
same incinerator but now we've got three
Gophers right there's a gopher whose job
is just to load the card there's a job
as a goal for his job is just to carry
the cart and then presumably return the
empty back again and then there's a
gopher whose job is to load the
incinerator so three golfers it's going
to go faster might not go much faster
though because they're going to get
blocked you know the cart the books is
going to be in the wrong place and
there's time to bring the Gopher running
back with the empty where nothing useful
is getting done with the car so let's
clean that up by having another go for
returning empties okay now this is
obviously so right but i want to point
something fairly profound that's going
on here this this version of the problem
will actually execute better than this
problem this guy even though we're
actually doing more work by having
another goal for running back and forth
in here so once we thought this
concurrency idea we're able to add
Gophers to the picture and actually do
more work but make it run faster because
the concurrent composition of better
managed pieces can actually run faster
and it's pretty unlikely that things
will work out just perfectly but you can
imagine that if all the Gophers were
time just right Nepal we're just right
and they knew how many books to move at
a time this thing could actually keep
all four gopher is busy at once and it
could in fact move four times faster
than our original version unlikely but I
want you understand that it's possible
so here's an observation it's really
important and it's kind of subtle we
improve the performance of this program
by adding a concurrent procedure to an
existing design we actually added more
things but the whole thing got faster
and if you think about it that's kind of
weird it's also kind of not weird
because you added another gopher and
Gophers to work but if you forget the
fact that he's a gopher and think of it
it's just adding design adding things
that the design can actually make it
more efficient and that parallelism can
come from better concurrent expression
of the problem it's a fairly deep
insight that doesn't look
like it because their Gophers involved
but that's okay so we have four
concurrent procedures running it right
there's a gopher that loads things into
the cart there's a gopher that takes the
cart and trucks it across towards the
incinerator there's another gopher who's
unloading the carts contents into the
incinerator and there's a fourth gopher
who is returning the empty carts back
and you can think of these as
independent procedures just running as
independent things completely and we
just compose those in parallel to
construct the entire program solution
but that's not the only way we could do
it here's a completely different design
sorry not going to do it we can we can
here's the same design made more
parallel by putting another pile of
books another in center in and for more
Gophers but you see the key point is you
we're taking the idea that we have how
we break the problem up and once we
understand is it's concurrent
decomposition we can actually paralyzed
on different axes and get better
throughput or or not but at least we
understand the problem in a much more
fine-grain way we have control over the
pieces in this case is if we get
everything just right we've got eight
Gophers working hard for us burning up
those C++ manuals or maybe there's no
paralyzation at all who says that all
these Gophers have to be busy at once I
might only be able to run one gopher at
a time in which case this design would
only run at the rate of a single gopher
like the original problem and the other
seven would all be idle while he's
running but the design is still correct
and that's a pretty big deal because it
means that we don't have to worry about
parallelism when we're doing concurrency
forget the concurrency right the
parallelism is actually a free variable
that we can decide just how many Gophers
are busy or we could do a completely
different design for the whole thing
let's let's forget the old pattern put
in a new pattern and we'll have to go
furs in the in the in the story but
instead of having one go for carry that
all the way from the pile to the
incinerator we put a staging dump in the
middle so the first gopher carries the
books to the to the dump drops them off
runs back gets more the second guy sits
there waiting for cart for books to
arrive to the pile takes them from there
was a new sinner ater and if you get
this right you've got to go for
procedures running but they're kind of
different procedures the kind of the
same but they're subtly different they
have slightly different parameters but
if you get this system running right at
least once it's
verted it can in fact in principle run
twice as fast as the original even
though it's a completely different
design in some in some sense the
original one but of course once we've
got this composition we can go another
way we can paralyze the usual way run
two versions of this whole program at
once and double again now we got four
golfers maybe up to four times the
throughput or we could take a different
way again and put the staging pile in
the middle into the original concurrent
multi gopher problem so now we've got
eight Gophers on the fly and books
getting burned a terrific right but
that's still not good enough because we
can paralyze on another dimension and go
full-on so here's 16 Gophers moving
those books to the burning pot and it
obviously this is is very very
simplistic and silly it's got Gophers in
its that makes it good but I want you
understand that conceptually this is
really how you think about running
things in parallel you don't think about
by running in parallel you think about
how you break the problem down into
independent components that you can
separate and understand and get right
and then compose to solve the whole
problem together so what does this all
mean well first of all there are many
ways you could do this I showed you just
a couple if you sit there with a
sketchbook you can probably come up with
50 more ways to have go for his move
books there's lots of different designs
they're not necessarily all equivalent
but they can all be made to work and you
can then take those concurrent designs
and refactor them rearrange them scale
them in different dimensions to get
different abilities to process the
problem and it's nice because however
you do this the correctness of your
algorithm for doing this is easy it
really it's not going to break I mean
they're just go firs but you know the
design is intrinsically safe because
you've done it that way however it's
this is obviously a stupid problem this
has no bearing on real work well
actually it does because if you take
this problem and you change the book
pile into some web content you change
the gophers into CPUs you change the
card to the networking or the
marshalling code or whatever it is you
need to run to move the data and then
the incinerator is the web proxy or
browser whoever you want to think about
the consumer of the data you've just
constructed the design for a web serving
architecture and you don't probably
don't think of your webs
architecture is looking like that but
the fact this is pretty much what it is
right and you can see by substituting
the pieces this is exactly the kind of
designs that you think about when you
talk about things like proxies and
forwarding agents and and buffers and
all that kind of stuff scaling up more
instances there on this drawing they
just don't think of it that way so
they're not intrinsically hard things to
understand Gophers can do it so can we
right so let me now show you how to use
these ideas a little bit in building
things with go now i don't i'm not going
to teach you go in this talk i hope some
of you know it already i hope lots of
you go and learn about more more about
it afterwards but i'm going to try to
teach you a little tiny bit ago and they
hope the rest car gets absorbed as we as
we do it so go has these things called
go routines which you can think of as
being a little bit like threads but
they're actually different and i rather
than go into the details of how the
different let's just say what they are
so let's say we have a function that in
this case takes two arguments if we call
that function are in our program then we
wait for the function to complete before
the next statement executes that's very
very familiar you all know that but if
instead you put the keyword go before
you call that function then what happens
is that function starts running but you
get to run right away at least
conceptually not necessarily remember
concurrency versus parallel but
conceptually your program keeps running
while f is off you're doing his thing
right and you don't have to wait for the
f to return and if you that seems
confusing just think of it as being a
lot like the ampersand in the show so
this is like running f ampersand off in
the background ok now what is exactly is
it go routine well they're kind of like
threads right because they run together
they're in the same address space within
a program they are at least but they're
much much cheaper it's pretty easy to
make them and they're very cheap to
create and then they get multiplex
dynamically onto operating system
threads as required so you don't have to
worry about scheduling unblocking and so
on the system takes care of that for you
and when a when a girl routine does need
to block like doing a read system call
or something like that no other girl
routine needs to wait for it they're all
scheduled than anything so they feel
like threads but there are much much
lighter weight version of them and it's
not an original idea that other
languages and systems have done things
like this but we we give them our own
name to make it clear what they are so
call them go routines okay now I
mentioned we have to communicate between
these things right so to do that we have
these things in go called channels which
are like a little bit like pipes in the
shell but they have types and they have
other nice properties which we're not
going to go into here today but here's a
fairly trivial example we create a timer
channel and say it's a channel of time
time values and then we launch this this
function in the background that we sleep
for a certain amount time delta T and
then sends on the timer channel the time
at that instant timer time now and then
the other process because the other guru
team because this one was launched with
a ghost statement can doesn't have to
wait it can do whatever it wants and
when it's ready to hear that the other
guys completed he says I want to receive
from the timer channel whatever that
value is and that goroutine will block
until there's a value to be delivered
and once it is completed that will be
get set to the time at which the other
go routine completed trivial example but
everything you need is in that one
little slide and then the last piece is
a thing called select and what it does
is that lets you control your programs
behavior by listening to the by looking
at multiple channels at once it seeing
who's ready to communicate and you can
decide to rate in this case between
channel 1 or channel 2 and the program
will behave differently depending on
weather channel 1 our channel two is
ready in this case if neither is ready
the default clause will run which is me
lets you sort of fall through if
nobody's ready to communicate if the
default clause is not present in the
Select then you'll wait until one or the
other of the channels is ready and if
they're both ready the system will just
pick one randomly so this this will come
up a little later but it's pretty much
like a switch statement but for
communications and if you know
Dijkstra's guarded commands it should
seem fairly familiar now I said go
sports concurrency I mean it really
supports concurrency it is routine in a
go program to create thousands of go
routines and we are once debugging
actually live at a conference go thing
that was running in production that he
created 1.3 million go routines and had
something on the neighborhood of ten
thousand actually active at the time we
were debugging it and to make this work
of course they have to be much much
cheaper than threads and that's kind of
the point so they're not free there's
allocation involved but not much and
they grow and shrink as needed and they
sort of well man
but they're very very cheap and you can
think about them as being you know as
cheap as Gophers you also need closures
I showed your closure sort of under the
covers before here is just proof that
you have them in the language because
they're very handy and concurrent
expressions of things to create nonce
procedures so you can create a function
that here in this case composes a couple
more function returns a function it's
just a show that it works and and
they're real closures you can go so
let's use these elements to build some
examples and I hope you'll learn a
little bit of concurrent go programming
by osmosis which is the best way to
learn so let's start by launching a
demon you can use a closure here to wrap
some background operation you want done
but not wait for it so in this case we
have two channels input and output and
for whatever reason we have to deliver
input to output we do but we don't want
to wait until the copying is done so we
say go funk for a closure and then have
a for loop that just reads the input
values and writes into the output and
the full range clause in go will drain
the channel so I'll run until the
channel is empty and then exit so this
little burst of code just drains the
channel automatically and does it in the
background so you don't have to wait for
anything I was a little bit of
boilerplate there but you get it's not
too bad and you get used to it let me
now show you a very simple load balancer
a very simple one and if there's time
which is I'm not sure there will be i'll
show you another one but this is a
simple one so imagine you have a bunch
of jobs that need to get done and we've
abstracted them away here or maybe
concretize them into a work structure
with three integer values that you need
to do some operation on so the worker
tasks what they're going to do is
compute something based on these values
and then I put a sleep in there so that
this guarantee do we have to think about
blocking because this is work attached
may block an arbitrary amount of time a
way we structure it is we have the
worker task read the input channel to
get work to do and have an output
channel to deliver the results so the
arguments of this function and then in
the loop we range over the input values
doing the calculation sleeping for some
essentially arbitrary time and then
delivering the output to the respond to
the output to the guy who's waiting so
we have to worry about blocking so
that's got to be pretty hard right well
there's the whole slew
and the reason this is so easy is that
the channels and the way they work along
with the other elements of the language
lets you express these concurrent things
and compose them really well this what
this does is creates two channels an
input channel in an output channel which
is the things connected to the worker
they're all reading off one input
channel and delivering to one output
channel and then you start up some
arbitrary number of workers notice the
go clause in the middle there all these
guys are running concurrently maybe in
parallel and then you start another job
up that says generate lots of work for
these guys to do and then you hang
around in this call function call
received lots of results which will read
the values coming out of the output
channel in the order that they complete
and because of the way this thing is
structured whether you're running on one
processor or a thousand if job will run
correctly and completely anyone use the
resource as well it's all taken care of
for you and if you think about this
problem it's pretty trivial but it's
actually fairly hard to write concisely
in most languages without concurrency
concurrency makes it pretty compacted to
do this kind of thing and more important
it's implicitly parallel although not
you don't have to think about problems
if you don't want to but it also can
scale really well there's no
synchronization or nonsense in their num
workers could be a huge number and the
thing would still work efficiently and
the tools of concurrency therefore make
it easy to build these kind of solutions
to fairly big problems also notice there
was no locking no mutexes all these
things that people think about when they
think about the old concurrency models
they're just not there you don't see
them and yet this is a correct
concurrent and parallelizable algorithm
with no locking in it that's got to be
good right but that was too easy so
let's see how we doing yeah I got I got
time to do the harder one this is a
little trickier but it's the same basic
idea but done much more realistically so
imagine we have a load balancer that we
want to write that's got a bunch of
requesters generating actual work okay
and we want we have a bunch of work of
tasks and we want to distribute all
these requesters workload on to an
arbitrary number of workers and make it
all sort of load balance out so the work
gets assigned to the least lightly
loaded worker so you can think of the
workers have up may have large amounts
of work they're doing it once it's not
just one at a time they may be doing
lots and the
lots of requests going on so it's a very
busy system this could be maybe on one
machine which is how i'm going to show
it to you but you could also imagine
that some of these these lines represent
network connections that are doing
proper load balancing architectural e
the design is still going to be safe the
way we do it so whatever crest looks
like is very different now we have some
arbitrary function closure if you like
that represents the calculation that we
want to do and we have a channel on
which we're going to return the result
now notice the channel is part of the
request in go unlike a lot of the other
languages like Erlang the channel idea
is there and it's a first-class value in
the language and that allows you to pass
channels around and they're kind of like
file descriptors in the sense if you
have the channel you can communicate
with someone but no1 with anyone who
does not have the channel is not able to
so it's like you know being able to pass
a phone call to somebody else to do or
to pass a file descriptor over a file
descriptor there it's pretty pretty
powerful idea so the idea is you're
going to send a request with a
calculation to do and a channel on which
to return the result once it's done so
what here's an artificial but somewhat
illustrative version of the requester
what we do is we have a channel of
requests that are coming in and we're
going to generate stuff to do on that
work channel so we make a channel which
is going to go inside each request to
come back to us for the answer we do
some work which I've just represented
here is sleeping who knows what what's
actually doing and then you send on the
work channel a request object with the
function you want to calculate and
whatever that is I don't care and a
channel I want you send the answer back
and then you wait for the on that
channel to get the result to come back
and once you've got that you probably
have to do something with the result so
this is just something generating work
at some arbitrary rate it's just
cranking out results but it's doing it
by communicating on channels with with
inputs and outputs and then the worker
it which is on the other side of this
picture remember we've got requesters
hoops we've got requesters living at the
balancer which is the last thing I'm
going to show you and workers on the
right what the workers have in them is a
channel of incoming requests and then a
count of pending tasks and
which is going to represent the load
that that worker has the number of tasks
he's actually got busy and then an index
which is part of the heap architecture
to show you in a second so then what the
worker does is receive work from his
requester his request channel which is
part of the worker object call the
function on the worker side so you pass
it request I guess from your father who
passed the actual function from the
requester through the balancer into the
worker he does the answer and then he
returns the the answer back on the
channel now notice that unlike a lot of
other load balancing architectures the
channels from the worker back to the
requester do not go through the load
balancer once the load once the
requester in the worker connected the
bouncers out of the picture and the work
on the requests are talking directly and
that's because you can pass channels
around inside the system as this running
okay and then if you want to you could
also put a go routine inside put a ghost
statement inside here and just run all
these requests in parallel on the worker
it would work just fine if you did that
but I that's enough going on at once
already okay and then the balance was
kind of magical you need a pool of
workers and need some balance or object
you're going to put the balancers
methods on and that concludes a pool and
then a single done channel which is how
the workers tell the balancer that
they've finished their most recent
calculation so then the balancer is
pretty easy what it does is it just
forever does a select statement waiting
either for more work to do from a
requester in which case it dispatches
that requests the most lightly loaded
worker or a worker tells them he's done
in which case you update the data
structure e by saying the balancer that
worker is complete his task so it's just
a simple two-way select and then we just
have to admit these two functions and to
do that we actually what we actually do
is construct a heap I'll skip that bits
are very exciting you get the idea
dispatch all this patch has to do is
grab the least loaded worker which is a
standard priority queue implementation
on the heap so you pull the most likely
little worker off the heap you send it
the task by writing the request to its
request channel now you increment the
load because it's got one more guy you
know about and that's going to influence
the loading distribution and then you
push it back onto the same spot on the
heat and go and
sit you've just dispatched it and you've
updated in structure and that's what for
executable lines of code and then the
completion task which is when the work
is finished you've got to do the sort of
inverse you there's one for your guy on
this work is Q so you decrement is
pending cow you pop in from the heat and
you put it back on the heat and that
will put them back where it belongs in
the priority queue and that's a complete
implementation of a semi-realistic load
balancer but the key point here is that
the data structures are using channels
and go routines to construct this
concurrent thing and the result is
scalable it's correct it's very simple
it's got no explicit locking and yet the
architecture just sort of makes it all
happen and concurrency is therefore
enabled the parallelism intrinsic in
this thing and you can actually run this
program i have this program it's all
compilable and runnable and it works and
it does the load balancing perf and the
things i'll stay at exactly uniform load
modulo quantization it's pretty good and
you can of course have I never said how
many workers there are or how many
questions there are there could be one
of each and 10 of the other or a
thousand of each or a million of each
the scaling still works and still
behaves efficiently one more example
which is somewhat more surprising but it
fits on a single slides with a nice one
to finish imagine you how to a
replicated database so you've got
database with with the same data in each
of multiple what we call shards at
Google saying the same instance right
and what you want to do is deliver a
quest to all the databases and a query
and get back the result but they're all
going to be the same you're using this
to go faster by picking the first guy to
answer as first got to come back with
the answer is the one you want so if one
of them's down or disconnected or
something you don't care because
somebody else will come in so here's how
to do that this is the full
implementation of it you have some array
of connections and some query you want
to execute you create a channel which is
buffered to the number of elements the
number of replicants inside this
replicas inside the the query database
and then you just run over all of the
connections to the databases and
for each one of them you start a go
routine up to deliver the query to that
channel to that database and then get
the answer back but by this Duke we
recall and then deliver the answer to
the single channel that that's holding
the results for all of these guys and
then after you've launched them all you
just wait on the bottom line there and
we wait for the the first guy that comes
back on the channel is the answer you
want you return it and you're done and
the thing is this looks like a toy and
it kind of is but it's actually a
complete correct implementation the one
thing that's missing is clean teardown
you want to sort of shut tell the
servers that haven't come back yet when
you've got an answer that you don't need
them anymore and you can do that is it's
more code but not an unreasonable amount
more but then it wouldn't fit on the
slide so I just want to show you this is
a fairly sophisticated problem to write
in a lot of systems but here it just
sort of falls naturally out of the
architecture because you've got the
tools of concurrency to represent a
fairly large distributed complex problem
and it works out really nicely so five
seconds left that's good conclusion
concurrency is powerful but it's not
parallelism but it enables parallelism
and it makes parallelism easy and if you
if you get that then I've done my job so
if you want to read more there's a bunch
of links here there's a goal enghadr
guys everything about go you want to
know there's a nice history paper that
rust put together that's linked there I
gave a talk a few years ago that led up
to us actually doing go which you might
find interesting Bob Harper at CMU has a
really nice blog posting called
parallelism is not concurrency which is
very similar to the idea that
concurrency is not parallelism but not
quite and then there's a couple other
things the most surprising thing on this
is the concurrent power series work that
Doug math were my old boss at Bell Labs
did which is an amazing amazing paper
but also if you want to be a different
spin on it the last link on the slide is
to another language God sawzall which I
did at Yale at ha at Google shortly
after coming there from Bell Labs and
it's remarkable because it is incredibly
parallel language but it has absolutely
no concurrency and by now I think you
might understand that that's possible so
thanks very much for listening and
thanks to eric for writing me and I guess it's time to have
some drinks or something
[Music]
you

上一篇:Jmeter系列(11)- 并发线程组Concurrency Thread Group详解


下一篇:DataFrame数据转为list,再逐行写入Excel