One CPU core only executes one task at any given time.
The illusion of "doing several things at the same time" is created by the OS or interrupts switching quickly between which tasks is currently being executed on that CPU core.
When an interrupt occurs, the CPU will shelve its current context (registers, program counter, etc) into memory to be able to resume later from where it was, then switch to the interrupt code, execute it, then once finished resume from where it was.
A multitasking OS expands on that, by having interrupts that can resume into another waiting task. So, for example if data is received on the serial port, an interrupt is raised, the OS reads the data, then checks if there was a task waiting for that. If so, it can give control to that task, otherwise to any other task waiting to execute.
If the current task does not yield, the OS will let it execute for a certain amount of time until a timer interrupt occurs, then switch to another waiting task, attempting to share available CPU time in a useful manner.
This introduces the notion of latency: when something happens (like a button press) what is the delay until your code executes to process the event? Sometimes you want that delay to be as low as possible, sometimes a guaranteed upper bound is alright, or you may want the delay to be as constant as possible. This latency also depends on how many other things may happen at the same time which also require processing.
Various ways to acquire a timestamp for a level change on a pin:
- Polling the pin with code
This will only notice the change when the code is executing, so when no other task or interrupt is executing instead. Thus the latency will depend on task load, other tasks priority, etc. This has the highest maximum latency, potentially milliseconds or more, and it wastes a lot of CPU time polling the pin.
The MCU is configured to raise an interrupt on level change on the pin. When that occurs, there is a short wait (few cycles to few tens of cycles depending on MCU) as the CPU stores its current execution context and switches to your interrupt code. However, if several other interrupts arrive at the same time, or if it was already processing a higher priority interrupt, then it'll have to finish that before executing your interrupt code checking the pin, which adds latency. If several pins change at the same time, who knows in what order the interrupts will execute.
Interrupt code should only do what needs to be done urgently, like storing the timestamp or input data in a FIFO. Then, later, a task can process it.
- Timer/counter in capture mode
This sidesteps the CPU, and this time, if you have several timers or capture units, they do run in parallel. When the level change occurs, the capture unit will store the current value of the counter (acting as timestamp) into a register, then raise an interrupt. This has latency, so the event will be processed later, but the event timestamp that was stored by the capture unit is as accurate as possible.
In this case the latency for capturing the timestamp is fixed, very low, and known. If several events happen at the same time, they can be captured at the same time, with an error of +/- 1 cycle depending when they happen relative to the time the counter increments.
This still does not guarantee interrupts will execute in the order of the incoming level change events, but the timestamps will be correct.