Multitasking: Types of Multitasking in Programming

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:

  • Process multitasking (based on processes — simultaneously running programs). Here a program is the smallest unit of managed code that the operating system's scheduler can control. It is better known to most users (working in a text editor and listening to music).
  • Thread multitasking (based on threads). The smallest unit of managed code is a thread (one program can perform 2 or more tasks at the same time).

Multithreading — a specialized form of multitasking

Properties of a multitasking environment

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:

  • Each task has its own priority, according to which it receives processor time and memory
  • The system organizes task queues so that all tasks receive resources, depending on priorities and the system's strategy
  • The system organizes interrupt handling, by which tasks can be activated, deactivated and removed
  • When the allotted time quantum ends, the kernel temporarily moves the task from the running state to the ready state, giving resources to other tasks. When memory is insufficient, pages of non-running tasks can be swapped out to disk (swapping), and then, after a system-defined time, restored to memory
  • The system provides protection of a task's address space from unauthorized interference by other tasks
  • The system provides protection of its kernel's address space from unauthorized interference by tasks
  • The system detects failures and hangs of individual tasks and terminates them
  • The system resolves resource and device access conflicts, preventing deadlocks caused by general hangs while waiting for blocked resources
  • The system guarantees every task that, sooner or later, it will be activated
  • The system processes real-time requests
  • The system provides communication between processes

Difficulties in implementing a multitasking environment

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.

History of multitasking operating systems

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

Types of pseudo-parallel multitasking

Multitasking: Types of Multitasking in Programming

Multitasking: Types of Multitasking in Programming

Simple switching

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.

Cooperative multitasking

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.

Preemptive, or priority-based, multitasking (real-time mode) Preemptive multitasking

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:

  • the possibility of fully implementing multitasking I/O in the OS kernel, where one program waiting for I/O to complete allows the processor to meanwhile execute another program;
  • a strong increase in the reliability of the system as a whole, combined with the use of memory protection — the ideal of “no user-mode program can disrupt the operation of the OS as a whole” becomes achievable at least in theory, whereas outside preemptive multitasking it is not achievable even in theory.
  • the ability to make full use of multiprocessor and multicore systems.

Disadvantages:

  • the need for special discipline when writing code, special requirements for its reentrancy, for protecting all shared and global data with objects such as critical sections and mutexes.

Implemented in operating systems such as:

  • VMS
  • MenuetOS
  • Linux
  • in user mode (and often in kernel mode too) of all UNIX-like operating systems, including versions of Mac OS X, iOS; Symbian OS
  • in kernel mode in Windows 3.x — only when running on a 386 processor or newer, where the “tasks” are only all Windows applications taken together, and each individual DOS virtual machine; preemptive multitasking was not used between Windows applications
  • Windows 95/98/ME — without full memory protection, which was the reason for the extremely low reliability of these OSes, on the same level as MS-DOS, Windows 3.x and Mac OS versions prior to X
  • Windows NT/2000/XP/Vista/7, both in kernel mode and in user mode.
  • AmigaOS — all versions, up to version 4.0 without full memory protection, which in practice had almost no effect on the reliability of system programs due to the high degree of standardization and transparent APIs and SDKs. Programs oriented toward the Amiga's “hardware”, on the contrary, were not distinguished by reliability.

Problem situations in multitasking systems

Starvation

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).

Race condition

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).

Priority inversion

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.

See also

  • [[b6807]]
  • [[b4699]]
  • [[b4698]]
  • [[b4674]]

See also

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Highly loaded projects. Theory of parallel computing. Supercomputers. Distributed systems"

Terms: Highly loaded projects. Theory of parallel computing. Supercomputers. Distributed systems