1

I've read various articles and similar questions, and I know that the two concepts are different, but I don't seem to know the correct answer.

I understood that thread is in terms of number of workers, and sync/async is in terms of task order. I would like to ask if my understanding is correct, with the following example.

Have to make a sandwich.

  1. Bake bread.
  2. Fry the eggs.
  3. Combine.

A thread == a frying pan.

  • Single-thread & sync:
    1. Put the bread on the frying pan.
    2. just watch until the bread is all baked.
    3. When the bread is baked, remove the bread from the pan and put the eggs on that pan.
  • Multi-thread & async:
    1. Multiple pans.
    2. Put the bread and eggs on different pans, respectively.
    3. No matter what you put first, take out the completed one.
  • Single-thread & async:
    1. One pan.
    2. Put the bread on the pan.
    3. The bread was not all baked, but put it aside for a while and put the eggs on that pan.
    4. The eggs aren't all fried, but I'll just put them away and put the bread on the pan.
    5. Repeat...
  • Multi-thread & sync:
    1. There are several pans, but we will bake the bread on pan1 first.
    2. When the bread on pan1 is finished, fry the eggs on pan2.

Is my understanding correct?

+) If so, case single-thread / async like javascript, the task in the event loop is just waiting in the queue and not progressing, right?

mishy
  • 123
  • 10

1 Answers1

1

The example is great & funny. Do not forget to take later the other one for "Multi-thread & async" otherwise it will be burnt ;) . Otherwise, It seems correct overall to me.

The example is not very good for the "Single-thread & async" case though and it might be the source of confusion. In practice, the pre-emption (switch between the eggs and the bread) is not guaranteed in async. It is generally done cooperatively. The problem is that in you example the bread cannot be cooked while the eggs are since there is only 1 pan. In practice, a task like I/Os operations can progress without using any core (ie. pan).

Async is mainly useful to avoid waiting a task completion while something else can be done. It is not really useful for computing tasks. For example, if you cook 2 sandwiches for 2 friends and you do not know if they like eggs or beacon, you need to ask them. This task can be async: you can ask the first, then the second, then cook the bread using 1 or 2 pan, then check the answers before cooking the eggs/beacon. Without async, you have to wait for the answer (possibly in a thread) before cooking the bread (which is not efficient).

Async operation can be split in multiple parts:

  1. starting the request (eg. send a message to a friend)
  2. checking the state or triggering events (eg. checking your friend messages or react to them when received)
  3. completing the request (eg. start cocking what your friends want) which can include starting new requests (done in 1.)

The part 2 is dependent of the language/framework. Moreover, regarding the language/framework, there is sometime a part consisting in waiting for the task to be completed (blocking operation). It can be done by looping on the part 2 until the state is completed, but sometime it can be done a bit more efficiently.

Jérôme Richard
  • 41,678
  • 6
  • 29
  • 59
  • Thank you for answer. But there seems to be something I don't understand. 1.`the switch between bread and egg is not async`, does that mean that the order is not guaranteed like `b - e - b - e` ? 2. Can I understand `the problem in your example` to mean that my example doesn't make assumptions about non-CPU(pan) tasks? – mishy May 09 '22 at 04:36
  • off topic) My understanding of I/O operations may be lacking. Isn't the process taking up CPU in Blocking I/O operation? Even if it is non-blocking, does not use the CPU in kernel mode for a while? – mishy May 09 '22 at 04:37
  • 1. Yes the order is not guaranteed. This is very dependent of the target framework / async method though. But generally, an async task will continue until it has to wait for some work, or until the work is completed. In that case, the task itself cooperatively temporary stop itself and let other ones run. This is very different from multithreading where the OS regularly switch between two threads when they compete for the same cores (pre-emption). A slow async process that is not very cooperative can prevent others to be executed for a long time. A possible execution is `b - b - e - e`. – Jérôme Richard May 09 '22 at 20:03
  • 2. I am not sure to understand your point. I would say that your example does not *consider* tasks other than CPU-intensive ones (ie. pan-greedy). Is it more clear? – Jérôme Richard May 09 '22 at 20:07
  • I/O operations are a bit complex and things changed over time. The method to deal with async I/O operations is dependant of the application, the OS, and the actual APIs. Blocking I/Os is quite the standard since the last decade but it is not the best solution. When an application use blocking operations for example to read a file, it make a request to the OS which add it to a queue that can be computed by another thread later or directly. Unless the OS can directly provide the result, the requesting thread is interrupted by the OS and it is put in a waiting list. – Jérôme Richard May 09 '22 at 20:14
  • When the operation is completed, the OS wake up the thread associated to the request and the blocking call returns. In general, the OS do not actively loop over events. It send requests to the devices and they use hardware interrupts to cause the execution of kernel functions that will decide to fill some queues, interrupt some threads and wake up some others. Kernel threads can actively fetch a memory location though they frequently wait so not to consume resources (and let user thread be executed). – Jérôme Richard May 09 '22 at 20:21
  • Some high-level async APIs use blocking calls internally and a not really useful. Some other use non-blocking calls and have a main even thread looping over all pending requests to check for their completion. None of the two approach are efficient though the second one is better as tasks does not wait. Mainstream OS provides async APIs to do that but they are not always faster than using the previous methods in practice. Linux recently provided a new fast way to do that efficiently very recently (see IO-Uring & Async-IO). – Jérôme Richard May 09 '22 at 20:31