Scripting Language Design, Concurrency, and Communication Patterns
Scripting Language Design Principles
Variable Declaration and Dynamic Typing
- Most scripting languages, with Scheme as a notable exception, do not require variables to be declared.
- Perl and JavaScript permit optional declarations, serving as a form of compiler-checked documentation.
- Perl can be run in a mode (
use strict 'vars'
) that mandates variable declarations. - With or without explicit declarations, most scripting languages employ dynamic typing. The interpreter performs type checking at runtime or coerces values when appropriate.
- Tcl is unusual in that all values, including lists, are internally represented as strings.
Nesting, Scoping, and Closures
- Nesting and scoping conventions vary significantly across languages.
- Scheme, Python, and JavaScript offer the classic combination of nested subroutines and static (lexical) scope.
- Tcl permits subroutines to nest but utilizes dynamic scope.
- Named subroutines (methods) do not nest in PHP or Ruby.
- Perl and Ruby, alongside Scheme, Python, and JavaScript, provide first-class anonymous local subroutines.
- Nested blocks are statically scoped in Perl. In Ruby, they are part of the named scope in which they appear.
- Scheme, Perl, and Python support variables captured in closures.
Namespace Rules and Information Hiding
- PHP and major glue languages (Perl, Tcl, Python, Ruby) all feature sophisticated namespace rules and mechanisms for information hiding and the selective import of names from separate modules.
- In Perl, all variables are global unless explicitly specified otherwise.
- In PHP, variables are local unless explicitly imported.
- Ruby has distinct levels:
$foo
is global,foo
is local,@foo
is an instance variable of the current object, and@@foo
is an instance variable of the current object’s class.
Communication Partner Naming
Addressing Communication Partners
To send or receive a message, one must generally specify its destination or source. Communication partners require names or references to one another. These names can refer directly to a thread or process, or alternatively, to an entry or port of a module, or to a socket or channel abstraction.
The concept of addressing messages to processes first appeared in Hoare’s original CSP proposal, and is also central to PVM and MPI. In PVM or MPI, each process has a unique integer ID, and every send or receive operation specifies the ID of the communication partner.
Message Sending Operations
A critical design consideration for a send operation is the extent to which it may block the caller. Once a thread initiates a send, when is it permitted to resume execution? Blocking can serve several purposes:
- Resource Management: Preventing a sender from overwhelming a receiver or exhausting system buffers.
- Failure Semantics: Ensuring that a message has been successfully delivered or acknowledged before the sender proceeds.
- Return Parameters: Allowing the sender to receive information back from the receiver as part of the send operation.
Message Receiving Mechanisms
A primary distinction in categorizing message reception mechanisms is between explicit receive operations and implicit receipt. The SR language, for instance, provides an implicit receipt mechanism.
Concurrency and Synchronization Terminology
Before delving into synchronization, it’s essential to understand the common nomenclature used for various activities within a computer system.
Key Concepts in Concurrent Systems
- Concurrent Tasks
- Unless explicitly stated otherwise, the term task refers to a concurrent unit of execution, such as a thread or a process.
- Atomic Operations
- In concurrent programming, an operation (or set of operations) is atomic if it appears to the rest of the system to occur instantaneously without interruption. Synonymous terms include: linearizable, indivisible, or uninterruptible.
- Non-Atomic Operations
- Incrementing and decrementing a variable are examples of non-atomic operations. For instance, the C expression
X++;
is typically translated by the compiler into three distinct instructions:
- Load the value of
X
from memory into a CPU register. - Increment the value in the CPU register.
- Store the updated value from the CPU register back to memory.
Critical Section and Mutual Exclusion
- Critical Section
- Concurrent accesses to shared resources can lead to unexpected or erroneous behavior. Therefore, parts of the program where a shared resource is accessed are protected. This protected segment is known as a critical section or critical region. Typically, a critical section accesses a shared resource (e.g., a data structure, peripheral device, or network connection) that would not operate correctly with multiple concurrent accesses.
- Mutual Exclusion (Mutex)
- In computer science, mutual exclusion is a property of concurrency control implemented to prevent race conditions. It is the requirement that one thread of execution never enters its critical section at the same time that another concurrent thread of execution enters its own critical section. The word mutex is often used as a short form for mutual exclusion.
The Message-Passing Concurrency Model
In the message-passing model, concurrent modules interact by sending messages to each other through a communication channel. Modules send off messages, and incoming messages to each module are queued for handling.
Message-Passing Examples
- Network Communication: Two computers (A and B) in a network communicating via network connections.
- Web Interaction: A web browser (A) and a web server (B), where A opens a connection to B, asks for a web page, and B sends the web page data back to A.
- Instant Messaging: An instant messaging client (A) and server (B).
- Local Process Communication: Two programs (A and B) running on the same computer whose input and output have been connected by a pipe, such as
ls | grep
typed into a command prompt.