Lecture
Multitasking in programming (eng. multitasking) — a property of an operating system or execution environment that provides the ability to process several tasks in parallel (or pseudo-parallel). True multitasking of an operating system is only possible in distributed computing systems.
There are 2 types of multitasking:
Multithreading — a specialized form of multitasking
Primitive multitasking environments provide pure “resource sharing”, where each task is assigned a specific area of memory, and the task is activated at strictly defined time intervals.
More advanced multitasking systems allocate resources dynamically, when a task starts up in memory or leaves memory depending on its priority and on the system's strategy. Such a multitasking environment has the following features:
The main difficulty in implementing a multitasking environment is its reliability, expressed in memory protection, handling of failures and interrupts, and prevention of hangs and deadlocks.
Besides reliability, a multitasking environment must be efficient. The resource costs of maintaining it must not: hinder processes from proceeding, slow down their work, or drastically limit memory.
At first, implementing multitasking operating systems presented a serious technical difficulty, which is why the adoption of multitasking systems was delayed, and for a long time after their introduction users preferred single-tasking ones.
Later, after several successful solutions appeared, multitasking environments began to improve, and are now used everywhere.
Multitasking in an operating system was first implemented during the development of the Multics operating system (1964). One of the first multitasking systems was OS/360 (1966 ), used for IBM's computers and their Soviet counterparts, the ES EVM. Development of the system was severely delayed, and for the initial period IBM put forward the single-tasking DOS in order to satisfy customers before OS/360 was fully put into operation. The system was criticized for its low reliability and difficulty of operation.
In 1969, based on Multics, the UNIX system was developed with a fairly neat algorithmic solution to the multitasking problem. Nowadays, dozens of operating systems have been built on the basis of UNIX.
On PDP-11 computers and their Soviet counterparts, the SM-4, the multitasking system RSX-11 was used (Soviet counterpart — OSRV SM EVM), along with the time-sharing system TSX-PLUS, which provided limited multitasking capabilities and a multi-user time-sharing mode, emulating for each user the single-tasking RT-11 (Soviet counterpart — RAFOS). This latter solution was quite popular because of the low efficiency and reliability of a full-fledged multitasking system.
A neat solution turned out to be the VMS operating system, originally developed for VAX computers (Soviet counterpart — SM-1700) as a development of RSX-11.
The world's first multimedia personal computer, the Amiga 1000 (1984), was originally designed with full hardware support for real-time preemptive multitasking in the AmigaOS operating system. In this case, hardware and software development proceeded in parallel, which led to AmigaOS remaining unsurpassed on personal computers for a long time in terms of its multitasking scheduler's quantum (1/50 second per context switch).
Multitasking was also provided by Microsoft in its Windows operating systems. Making use of VMS's experience gave these systems significantly higher performance and reliability. In terms of multitasking context-switch time (quantization), only these operating systems can be compared with AmigaOS and UNIX (as well as its descendants, such as the Linux kernel).
Interestingly, multitasking can be implemented not only at the operating-system level but also at the language level. For example, the specifications of the Modula-2 and Ada programming languages require support for multitasking independent of any particular operating system. As a result, the implementation of the Modula-2 programming language TopSpeed by JPI/Clarion, popular in the first half of the 1990s, made it possible to organize various types of multitasking (cooperative and preemptive — see below) for threads of a single program within the framework of such a fundamentally single-tasking operating system as MS-DOS. This was achieved by including in the program module a compact task scheduler containing a timer-interrupt handler. Programming languages possessing this property are sometimes called real-time languages


A type of multitasking in which the operating system simultaneously loads two or more applications into memory, but processor time is given only to the main application. To run the background application, it must be activated. This kind of multitasking can be implemented not only in the operating system but also by means of task-switching programs. Well known in this category is the DESQview program, which ran under DOS and was first released in 1985.
Advantages: it lets you use already-existing programs written without multitasking in mind.
Disadvantages: impossible in non-interactive systems that operate without human involvement. Interaction between programs is extremely limited.
A type of multitasking in which the next task is executed only after the current task explicitly declares itself ready to give up processor time to other tasks. As a special case, such a declaration is implied when attempting to acquire an already-held mutex object (Linux kernel), as well as when waiting for the next message from the user-interface subsystem (Windows versions up to and including 3.x, as well as 16-bit applications in Windows 9x).
Cooperative multitasking can be called “second-stage” multitasking, since it uses more advanced methods than simple task switching, as implemented by many well-known programs (for example, DOS Shell from MS-DOS 5.0). With simple switching, the active program gets all the processor time, and background applications are completely frozen. With cooperative multitasking, an application can effectively seize as much processor time as it deems necessary. All applications share processor time, periodically passing control to the next task.
Advantages of cooperative multitasking: there is no need to protect all shared data structures with objects such as critical sections and mutexes, which simplifies programming, especially porting code from single-tasking environments to multitasking ones.
Disadvantages: the inability of all applications to keep working if an error occurs in one of them, resulting in a failure to call the “yield processor time” operation. Extremely difficult to implement a multitasking I/O architecture in the OS kernel that would allow the processor to execute one task while another task has initiated an I/O operation and is waiting for it to complete.
A type of multitasking in which the operating system itself passes control from one running program to another when I/O operations complete, hardware events occur in the computer, timers and time quanta expire, or certain signals arrive from one program to another. In this type of multitasking, the processor can be switched from executing one program to executing another without any wish of the first program, and literally between any two instructions in its code. Processor time is allocated by the process scheduler. In addition, each task can be assigned a specific priority by the user or by the operating system itself, which provides flexible management of processor-time distribution among tasks (for example, you can lower the priority of a resource-intensive program, thereby slowing down its operation but increasing the performance of background processes). This type of multitasking provides a faster response to user actions.
Advantages:
Disadvantages:
Implemented in operating systems such as:
The delay in time from when a thread wakes up until it is called onto the processor, during which it remains in the list of threads ready for execution. It arises due to the presence of threads with equal or higher priorities that are executing throughout that time.
The negative effect is that a delay arises between the thread waking up and its performing the next important operation, which delays the execution of that operation, and consequently the work of many other components as well.
Starvation creates a bottleneck in the system and prevents squeezing maximum performance out of it, limited only by hardware-imposed bottlenecks.
Any starvation outside of 100% processor load can be eliminated by raising the priority of the starving thread, possibly — temporarily.
As a rule, to prevent starvation, the OS automatically schedules ready low-priority threads for execution even when high-priority ones are present, provided the thread has not run for a long time (~10 seconds). This picture is visually familiar to most Windows users — if a thread in one of the programs has gone into an infinite loop, the foreground window still works normally despite this — Windows raises the priority of the thread associated with the foreground window. The other windows, however, are redrawn with large delays, at a rate of one portion per second, because their rendering in this situation works only thanks to the starvation-prevention mechanism (otherwise it would starve forever).
Nondeterministic order of execution of two threads of code that process the same data, running in two different threads (tasks). This leads to the order and correctness of execution depending on random factors.
It is eliminated by adding the necessary locks and synchronization primitives. It is usually an easily fixed defect (a forgotten lock).
Thread L has low priority, thread M — medium, thread H — high. Thread L acquires a mutex, and, while running while holding the mutex, is preempted by thread M, which woke up for some reason and has a higher priority. Thread H attempts to acquire the mutex.
In the resulting situation, thread H waits for thread M to finish its current work, because, while thread M is running, the low-priority thread L does not get control and cannot release the mutex.
It is eliminated by raising the priority of all threads holding the given mutex to one and the same high value for the duration of holding the mutex. Some mutex implementations do this automatically. Alternatively, raising the priority of the thread that has already acquired the mutex occurs after an attempt to simultaneously acquire the mutex by a higher-priority thread.
Comments