I am reading a book on RxJava, and following is an exceprt from it in context of async vs sync:
Thus, the actual criteria that is generally
important is whether the Observable event production is blocking or nonblocking,
not whether it is synchronous or asynchronous.
So what is the difference between blocking/nonblocking and sync/async?
Related
I have read tutorial and googled for this question, but still have some confuse. here is my understand about their difference, but I'm not sure whether it's right.
callback is also an synchronous style
synchronous and callback grpc cpp itself manage request and response queue and thread model, but asynchronous let user provide a thread management, am I right?
sync and callback sytle is not multiple threaded, am I right?
synchronous and callback have same performance, but asynchronous style can archive very high performance?
I have the following problem:
I'm trying to call sync closure from async function, but sync closure has to later call another async function.
I cannot make async closure, because they are unstable at the moment:
error[E0658]: async closures are unstable
so I have to do it this way somehow.
I found a couple of questions related to the problem, such as this, but when I tried to implement it, im receiving the following error:
Cannot start a runtime from within a runtime.
This happens because a function (like `block_on`)
attempted to block the current thread while the
thread is being used to drive asynchronous tasks.'
here is playground link which hopefully can show what I'm having problem with.
I'm using tokio as stated in the title.
As the error message states, Tokio doesn't allow you to have nested runtimes.
There's a section in the documentation for Tokio's Mutex which states the following (link):
[It] is ok and often preferred to use the ordinary Mutex from the standard library in asynchronous code. [...] the feature that the async mutex offers over the blocking mutex is that it is possible to keep the mutex locked across an .await point, which is rarely necessary for data.
Additionally, from Tokio's mini-Redis example:
A Tokio mutex is mostly intended to be used when locks need to be held
across .await yield points. All other cases are usually best
served by a std mutex. If the critical section does not include any
async operations but is long (CPU intensive or performing blocking
operations), then the entire operation, including waiting for the mutex,
is considered a "blocking" operation and tokio::task::spawn_blocking
should be used.
If the Mutex is the only async thing you need in your synchronous call, it's most likely better to make it a blocking Mutex. In that case, you can lock it from async code by first calling try_lock(), and, if that fails, attempting to lock it in a blocking context via spawn_blocking.
As stated in the ReactiveX Introduction - Observables Are Less Opinionated
ReactiveX is not biased toward some particular source of concurrency or asynchronicity. Observables can be implemented using thread-pools, event loops, non-blocking I/O, actors (such as from Akka), or whatever implementation suits your needs, your style, or your expertise. Client code treats all of its interactions with Observables as asynchronous, whether your underlying implementation is blocking or non-blocking and however you choose to implement it.
I am not getting the part - "whether your underlying implementation is blocking or non-blocking".
Can you explain more of it? Or some example code to explain this?
Observable.fromCallable(() -> doSomeReallyLongNetworkRequest())
.subscribe(data -> {
showTheDataOnTheUI(data);
});
Where do you think the doSomeReallyLongNetworkRequest() will run (thread-wise)?
Well if you will run this code at main thread, the network call will run at main thread!
"Observables Are Less Opinionated" means that the multi threading is abstracted away from the actual work. the Subscriber don't know (and don't need to), where the Observable will run, it can run on thread-pool, event loop, or it can even run in blocking fashion.
That's why all Observable interaction happen with async API.
While putting it this way seems like a drawback, the opposite is true, that means that you have greater control where each part of your code is run, without exposing the operation itself and the code that react to Observable emission to this knowledge.
This is done using the Schedulers mechanism in RxJava, together with subscribeOn()/observeOn() operators:
Observable.fromCallable(() -> doSomeReallyLongNetworkRequest())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
showTheDataOnTheUI(data);
});
now you told the Observable to perform the subscription code (doSomeReallyLongNetworkRequest()) to run on IO Schdeuler that will create a dedicated thread for the network request, and on the other side, you told the Observable to notify the about emissions (onNext()) Subscriber (showTheDataOnTheUI(data)) at main thread (sorry for android specifics).
With this approach you have very powerful mechanism to determine where and how both operations will work and where notifications will be fired, and very easily ping pong between different threads, this great power comes because of the async API, plus the abstraction of threads away to dedicated operators and Scheduler mechanism.
UPDATE: further explanation:
Client code treats all of its interactions with Observables as
asynchronous
Client code here means any code that interacts with the Observable, the simple example is the Subscriber which is the client of the Observable, as of the composable nature of Observable, you can get an Observable from some API without knowing exactly how its operate so :
Observable.fromCallable(() -> doSomeReallyLongNetworkRequest())
can be encapsulate with some service API as Observable<Data>, and when Subscriber interacts with it, it's happen with async fashion using the Observable events onNext,onError,onComplete.
whether your underlying implementation is blocking or non-blocking and
however you choose to implement it.
"underlying implementation" refers to the operation the Observable do, it can be blocking work like my example of network call, but it can also be a notifications from the UI (clicks events), or update happens at some external module, all of which are non-blocking implementation, and again as of its async API nature the Subscribe shouldn't care, it just need to react to notifications without worrying about where and how the implementation (Observable body) will act.
I often time hear the term asynchronous or non-blocking operations among the web domain. Usually they say that by encapsulating the time consuming operations as a Futures with the callbacks, threads does not block rather is notified when the callback returns.
How does this invoking a operation on the Future objects and being notified when the callback returns work under the hood with the web application context?
Thanks!
How do all the languages implements asynchronous callbacks?
For example in C++, one need to have a "monitor thread" to start a std::async. If it is started in main thread, it has to wait for the callback.
std::thread t{[]{std::async(callback_function).get();}}.detach();
v.s.
std::async(callback_function).get(); //Main thread will have to wait
What about asynchronous callbacks in JavaScript? In JS callbacks are massively used... How does V8 implement them? Does V8 create a lot of threads to listen on them and execute callback when it gets message? Or does it use one thread to listen on all the callbacks and keep refreshing?
For example,
setInterval(function(){},1000);
setInterval(function(){},2000);
Does V8 create 2 threads and monitor each callback state, or it has a pool thing to monitor all the callbacks?
V8 does not implement asynchronous functions with callbacks (including setInterval). Engine simply provides a way to execute JavaScript code.
As a V8 embedder you can create setInterval JavaScript function linked to your native C++ function that does what you want. For example, create thread or schedule some job. At this point it is your responsibility to call provided callback when it is necessary. Only one thread at a time can use V8 engine (V8 isolate instance) to execute code. This means synchronization is required if a callback needs to be called from another thread. V8 provides locking mechanism is you need this.
Another more common approach to solve this problem is to create a queue of functions for V8 to execute and use infinite queue processing loop to execute code on one thread. This is basically an event loop. This way you don't need to use execution lock, but instead use another thread to push callback function to a queue.
So it depends on a browser/Node.js/other embedder how they implement it.
TL;DR: To implement asynchronous callback is basically to allow the control flow to proceed without blocking for the callback. Before the callback function is finally called, the control flow is free to execute anything that has no dependence on the callback's result, e.g., the caller can proceed as if the callback function has returned, or the caller may yield its control to other functions.
Since the question is for general implementation rather than a specific language, my answer tries to be as general as to cover the implementation commonalities.
Different languages have different implementations for asynchronous callbacks, but the principles are the same. The key is to decouple the control flow from the code executed. They correspond to the execution context (like a thread of control with a runtime stack) and the executed task. Traditionally the execution context and the executed task are usually 1:1 associated. With asynchronous callbacks, they are decoupled.
1. The principles
To decouple the control flow from the code, it is helpful to think of every asynchronous callback as a conditional task. When the code registers an asynchronous callback, it virtually installs the task's condition in the system. The callback function is then invoked when the condition is satisfied. To support this, a condition monitoring mechanism and a task scheduler are needed, so that,
The programmer does not need to track the callback's condition;
Before the condition is satisfied, the program may proceed to execute other code that does not depend on the callback's result, without blocking on the condition;
Once the condition is satisfied, the callback is guaranteed to execute. The programmer does not need to schedule its execution;
After the callback is executed, its result is accessible to the caller.
2. Implementation for Portability
For example, if your code needs to process the data from a network connection, you do not need to write the code checking the connection state. You only registers a callback that will be invoked once the data is available for processing. The dirty work of connection checking is left to the language implementation, which is known to be tricky especially when we talk about scalability and portability.
The language implementation may employ asynchronous io, nonblocking io or a thread pool or whatever techniques to check the network state for you, and once the data is ready, the callback function is then scheduled to execute. Here the control flow of your code looks like directly going from the callback registration to the callback execution, because the language hides the intermediate steps. This is the portability story.
3. Implementation for Scalability
To hide the dirty work is only part of the whole story. The other part is that, your code itself does not need to block waiting for the task condition. It does not make sense to wait for one connection's data when you have lots of network connections simultaneously and some of them may already have data ready. The control flow of your code can simply register the callback, and then moves on with other tasks (e.g., the callbacks whose conditions have been satisfied), knowing that the registered callbacks will be executed anyway when their data are available.
If to satisfy the callback's condition does not involve much of the CPU (e.g., waiting for a timer, or waiting for the data from network), and the callback function itself is light-weighted, then single CPU (or single thread) is able to process lots of callbacks concurrently, such as incoming network requests processing. Here the control flow may look like jumping from one callback to another. This is the scalability story.
4. Implementation for Parallelism
Sometimes, the callbacks are not pending for non-blocking IO condition, but for blocking operations such as page fault; or the callbacks do not rely on any condition, but are pure computation logics. In this case, asynchronous callback does not save you the CPU waiting time (because there is no idle waiting). But since asynchronous callback implies that the callback function can be executed in parallel with the caller or other callbacks (subject to certain data sharing and synchronization constraints), the language implementation can dispatch the callback tasks to different threads, achieving the benefits of parallelism, if the platform has more than one hardware thread context. It still improves scalability.
5. Implementation for Productivity
The productivity with asynchronous callback may not be very positive when the code need to deal with chained callbacks, i.e., when callbacks register other callbacks in recursive way, known as callback hell. There are ways to rescue.
The semantics of an asynchronous callback can be explored so as to substitute the hopeless nested callbacks with other language constructs. Basically there can be two different views of callbacks:
From data flow point of view: asynchronous callback = event + task.
To register a callback essentially generates an event that will emit
when the task condition is satisfied. In this view, the chained
callbacks are just events whose processing triggers other event
emission. It can be naturally implemented in event-driven
programming, where the task execution is driven by events. Promise
and Observable may also be regarded as event-driven concept. When
multiple events are ready concurrently, their associated tasks can
be executed concurrently as well.
From control flow point of view: to register a callback yields the
control to other code, and the callback execution just resumes the
control flow once its condition is satisfied. In this view, chained
asynchronous callbacks are just resumable functions. Multiple
callbacks can be written as one after another in traditional
"synchronous" way, with yield operation in between (or await). It
actually becomes coroutine.
I haven't discussed the implementation of data passing between the asynchronous callback and its caller, but that is usually not difficult if using shared memory where caller and callback can share data. Actually Golang's channel can also be considered in line of yield/await but with its focus on data passing.
The callbacks that are passed to browser APIs, like setTimeout, are pushed into the same browser queue when the API has done its job.
The engine can check this queue when the stack is empty and push the next callback into the JS stack for execution.
You don’t have to monitor the progress of the API calls, you asked it to do a job and it will put your callback in the queue when it’s done.