Bogotobogo
contact@bogotobogo.com
Bookmark and Share




Multi-Threaded Programming
Terminology - 2012
cplusplus logo
Full List of C++ Tutorials


SoMaeMoolDo




Multi-Threaded Programming - Terminology

Thread

More exactly it is Thread of Execution which is the smallest unit of processing.

  1. It is scheduled by an OS.
  2. In general, it is contained in a process.
  3. So, multiple threads can exist within the same process.
  4. It shares the resources with the process: The memory, code (instructions), and global variable (context - the values that its variables reference at any given moment).
  5. On a single processor, each thread has its turn by multiplexing based on time. On a multiple processor, each thread is running at the same time with each processor/core running a particular thread.


Your Ad Here


Threads vs. Processes

Processes and threads are related to each other but are fundamentally different.

A process can be thought of as an instance of a program in execution. Each process is an independent entity to which system resources such as CPU time, memory, etc. are allocated and each process is executed in a separate address space. If we want to access another process' resources, inter-process communications have to be used such as pipes, files, sockets etc. See more on Linux process.


process


A thread uses the same address space of a process. A process can have multiple threads. A key difference between processes and threads is that multiple threads share parts of their state. Typically, multiple threads can read from and write to the same memory (no process can directly access the memory of another process). However, each thread still has its own stack of activation records and its own copy of CPU registers, including the stack pointer and the program counter, which together describe the state of the thread's execution.

A thread is a particular execution path of a process. When one thread modifies a process resource, the change is immediately visible to sibling threads.

  1. Processes are independent while thread is within a process.
  2. Processes have separate address spaces while threads share their address spaces.
  3. Processes communicate each other through inter-process communication.
  4. Processes carry considerable state (e.g., ready, running, waiting, or stopped) information, whereas multiple threads within a process share state as well as memory and other resources.
  5. Context switching between threads in the same process is typically faster than context switching between processes.
  6. Multithreading has some advantages over multiple processes. Threads require less overhead to manage than processes, and intraprocess thread communication is less expensive than interprocess communication.
  7. Multiple process concurrent programs do have one advantage: Each process can execute on a different machine (distribute program). Examples of distributed programs are file servers (NFS), file transfer clients and servers (FTP), remote log-in clients and servers (Telnet), groupware programs, and Web browsers and servers.

athread


fork() system call vs. creating a thread

We need to be clear about the difference between the fork() system call and the creation of new threads.

When a process executes a fork() call, a new copy of the process is created with its own variables and its own process id (PID), and this new process is scheduled independently, and executed almost independently of the parent process.

When we create a new thread within a process, on the other hand, the new thread gets its own stack (local variables) but shares global variables, file descriptors, signal handlers, and its current directory state with the process which created it.



Advantage of Multi-Threading
  1. Faster on a multi-CPU system.
  2. Even in a single CPU system, application can remain responsive by using worker thread runs concurrently with the main thread.


Identifying Multithread Opportunities

So, multithreading is a good thing. How can we identify multithreading oppurtinities in codes?

  1. We need runtime profile data of our application. Then, we can identify the bottleneck of code.
  2. Eaxmine the region, and check for dependencies. Then, determine whether the dependencies can be broken into either
    • multiple parallel task, or
    • loop over multiple parallel iteration.
  3. At this stage, we may consider a different algorithm.
  4. We need to estimate the overhead and performance gains. Will it give us linear scaling with the number of thread?
  5. If the scaling does not look promising, we may have to broaden the scope of our analysis.


Context Switch

Switching the CPU from one process or thread to another is called context switch. It requires saving the state of the old process or thread and loading the state of the new one. Since there may be several hundred context switches per second, context switches can potentially add significant overhead to an execution.



Race Condition

This happens when a critical section is not executed atomically.
An execution of threads depends on shared state. For example, two threads share variable i and trying to increment it by 1. It is highly depending on when they get it and when they save it.



Deadlock

Two or more competing actions are waiting for other to finish. No threads are changing their states.

In other words, deadlock occurs when some threads are blocked to acquire resources held by other blocked threads. A deadlock may arise due to dependence between two or more threads that request resources and two or more threads that hold those resources.

Example 1: Alphonse and Gaston are friends, and great believers in courtesy. A strict rule of courtesy is that when you bow to a friend, you must remain bowed until your friend has a chance to return the bow. Unfortunately, this rule does not account for the possibility that two friends might bow to each other at the same time.

Example 2: Two threads want to acquire mutex locks A and B to finish their task. Suppose thread 1 has already acquired lock A and thread 2 has already acquired B. Then, thread 1 cannot make progress because it is waiting for lock B, and thread 2 cannot make progress because it is waiting for lock A. So, the two thread are in deadlock.


deadlock


Livelock

A situation that process is not progressing. Example: When two people meet at narrow path, and both of them are repeatedly trying to yield to the other person. Both are changing their states but with no progress.

A thread often acts in response to the action of another thread. If the other thread's action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked - they are simply too busy responding to each other to resume work.

In the following example, the code was written in an attempt to avoid deadlock, however, if the two threads executed at the same time, they will be trapped in a livelock of constantly acquiring and releasing mutexes. It is very unlikely that either will make progress. Each thread acquires a lock and then attempts to acquire the other lock. If it fails to acquire the other lock, it releases the lock it is holding before another try to get both locks again. The thread exits the loop when it manages to acquire both locks, and it may happen, but until then, the application will make no progress:


livelock



Starvation

When a process having been denied necessary resources. Without the resources the program can not finish.

Example: An object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.



Dining Philosophers Problem

The dining philosophers problem is summarized as five philosophers sitting at a table doing one of two things: eating or thinking. While eating, they are not thinking, and while thinking, they are not eating. The five philosophers sit at a circular table with a large bowl of spaghetti in the center. A fork is placed in between each pair of adjacent philosophers, and as such, each philosopher has one fork to his left and one fork to his right. As spaghetti is difficult to serve and eat with a single fork, it is assumed that a philosopher must eat with two forks. Each philosopher can only use the forks on his immediate left and immediate right.

Dining_philosophers

source wiki

The philosophers never speak to each other, which creates a dangerous possibility of deadlock when every philosopher holds a left fork and waits perpetually for a right fork (or vice versa).

Originally used as a means of illustrating the problem of deadlock, this system reaches deadlock when there is a 'cycle of unwarranted requests'. In this case philosopher P1 waits for the fork grabbed by philosopher P2 who is waiting for the fork of philosopher P3 and so forth, making a circular chain.

Starvation (and the pun was intended in the original problem description) might also occur independently of deadlock if a philosopher is unable to acquire both forks because of a timing problem. For example there might be a rule that the philosophers put down a fork after waiting five minutes for the other fork to become available and wait a further five minutes before making their next attempt. This scheme eliminates the possibility of deadlock (the system can always advance to a different state) but still suffers from the problem of livelock. If all five philosophers appear in the dining room at exactly the same time and each picks up the left fork at the same time the philosophers will wait five minutes until they all put their forks down and then wait a further five minutes before they all pick them up again.

In general the dining philosophers problem is a generic and abstract problem used for explaining various issues which arise in problems which hold mutual exclusion as a core idea. The various kinds of failures these philosophers may experience are analogous to the difficulties that arise in real computer programming when multiple programs need exclusive access to shared resources. These issues are studied in the branch of Concurrent Programming. The original problems of Dijkstra were related to external devices like tape drives. However, the difficulties studied in the Dining Philosophers problem arise far more often when multiple processes access sets of data that are being updated. Systems that must deal with a large number of parallel processes, such as operating system kernels, use thousands of locks and synchronizations that require strict adherence to methods and protocols if such problems as deadlock, starvation, or data corruption are to be avoided.



Mutex (Mutual Exclusion)

There are two types of synchronization:

  1. Mutual Exclusion
    Mutual exclusion ensures that a group of atomic actions (critical section can not be executed by more than one thread at a time).

  2. Condition Synchronization
    This ensures that the state of a program satisfies a particular condition before some action occurs. For example, in the bank account problem, there is a need for both condition synchronization and mutual exclusion. The balance must not be in an empty condition before method withdraw() is executed, and mutual exclusion is required for ensuring that the withdraw not be executed more than once.

It is used to avoid simultaneous use of resources such as global variables by the critical sections. A Critical section is a piece of code where a process or thread accesses a common resource.

Mutex locks ensure that only one thread has access to a resource at a time. If the lock is held by another thread, the thread attempting to acquire the lock will sleep until the lock is released. A timeout can also be specified so that lock acquisition will fail if the lock does not become available within the specified interval. One problem with this approach is that it can serialize a program. This causes the multithreaded program to have only a sigle executing thread, which stops the program from taking advantage of multiple cores.

If multiple threads are waiting for the lock, the order in which the waiting threads will acquire the mutex is not guaranteed. Mutexes can be shared between processes. In comparison, critical sections cannot be shared between processes; consequently, performance overhead of critical sections is lower.

Spin locks are essentially mutex locks. The difference between a mutex lock and a spin lock is that a thread waiting to acquire a spin lock will keep trying to acquire the lock without sleeping. On the contrary, a mutex lock may sleep if it is unable to acquire the lock. The advantage of using spin locks is that they will acquire the lock as soon as it is released, while a mutex lock will need to be woken by the OS before it can get the lock. The disadvantage is that a spin lock will spin on a virtual CPU monopolizing that resource, but a mutex lock will sleep and free the CPU for another thread to use. So, in practice, mutex locks are often implemented to be a hybrid of a spin locks and more traditional mutex locks.



Critical Section

A code segment that accesses shared variable (or other shared resources) and that has to be executed as an atomic action is referred to as a critical section.

while (true) {
	entry-section
	critical section 	//accesses shared variables
	exit-section
	noncritical section
}

The entry- and exit-sections that surround a critical section must satisfy the following correctness requirements:

  • Mutual exclusion
    When a thread is executing in its critical section, no other threads can be executing in their critical sections.
  • Progress
    If no thread is executing in its critical section and there are threads that wish to enter their critical sections, only the threads that are executing in their entry- or exit-sections can participate in the decision about which thread will enter its critical section next, and this decision cannot be postponed indefinitely.
  • Bounded waiting
    After a thread makes a request to enter its critical section, there is a bound on the number of times that other threads are allowed to enter their critical sections before this thread's request is granted.

Critical sections are similar to mutex locks. The difference is that critical sections cannot be shared between processes. Therefore, their performance overhead is lower. Critical sections also have a different interface from that provided by mutex locks. Critical sections do not take a timeout value but do have an interface that allows the calling thread to try to enter the critical section. If this fails, the call immediately returns, enabling the thread to continue execution. They also have the facility of spinning for a number of iterations before the thread goes to sleep in the situation where the thread is unable to enter the critical section.





Slim Reader/Writer Locks

Slim reader/writer locks provide support for the situation where there are multiple threads that read shared data, but on rare occasions the shared data needs to be written. Data that is being read can be simultaneously accessed by multiple threads without concern for problems with corruption of the data being shared. However, only a single thread can have access to update the data at any one time, and other threads cannot access that data during the write operation. This is to prevent threads from reading incomplete or corrupted data that is in the process of being written. Slim reader/write locks cannot be shared across processes.



Semaphores

Semaphores are counters that can be either incremented or decremented. They can be used in situations where there is a finite limit to a resource and a mechanism is needed to impose that limit. An example is a buffer that has a fixed size. Whenever an element is added to a buffer, the number of available positions is decreased. Every time an element is removed, the number available is increased.

Semaphores can also be used to mimic mutexes. If there is only one element in the semaphore, then it can be either acquired or available, exactly as a mutex can be either locked or unlocked.

Semaphores will also signal or wake up threads that are waiting on them to use available resources. So, they can be used for signaling between threads.

Semaphores are used to provide mutual exclusion and condition synchronization.

It is a variable either a binary semaphore (true or false) or a counter (counting) semaphore. Semaphore is used to prevent race condition. Semaphores provide a means of restricting access to a finite set of resources or of signaling that a resource is available. As in the case with mutex locks, semaphores can be shared across processes.

  • Counting Semaphore

    A counting semaphore is a synchronization object that is initialized with an integer value and then accessed through two operations, named P and V, meaning down, up or decrement, increment, wait, signal, respectively (Dijkstra).
    class countingSemaphore {
    public:
    	countingSemaphore(int initialPermits) {
    		permits = initialPermits;
    	}
    	void P() {}
    	void V() {}
    private:
    	int permits;
    };
    
    void countingSemaphore::P() {
    	if(permits > 0)
    		--permits;	// take a permit from the pool
    	else			// the pool is empty so wait for a permit
    		wait until permits becomes positive and the decrement permits by one.
    }
    
    void CountingSemaphore::V() {
    	++permits;		// return a permit to the pool
    }
    
    
    It is helpful to interpret a counting semaphore as having a pool of permits. A thread calls method P() to request a permit. If the pool is empty, the thread waits until a permit becomes available. A thread calls method V() to return a permit to the pool. A counting semaphore s is declared and initialized using
    countingSemaphore s(1);
    
    The initial value, in this case 1, represents the initial number of permits in the pool. For a counting semaphore s, at any time, the following relation holds:
    (the initial number of permits) + (the number of completed s.V() operations)
     >= (the number of completed s.P() operations
    
    This relation is referred to as the invariant for semaphore s. Counting semaphore rely on a semaphore invariant to define its behavior.


  • Binary Semaphore
    A semaphore named mutex is initialized with the value of 1. The calls to mutex.P() and mutex.V() create a critical section:
    	Thread1			Thread2
    	----------------------------------
    	mutex.P();		mutex.P();
    	/*critial section*/ 	/*critical section*/
    	mutex.V();		mutex.V();
    
    Due to the initial value 1 for mutex and the placement of mutex.P() and mutex.V() around the critical section, a mutex.P() operation will be completed first, then mutex.V(), and so on. For this pattern, we can let mutex be a counting semaphore, or we can use a more restrictive type of semaphore called a binary semaphore.


Monitors

Semaphores were defined before the introduction of programming concepts such as data encapsulation and information hiding. In semaphore-based programs, shared variables and the semaphores that protect them are global variables. This causes shared variable and semaphore operations to be distributed throughout the program.

Since P and V operations are used for both mutual exclusion and condition synchronization, it is difficult to determine how a semaphore is being used without examining all the code.

Monitors were invented to overcome these problems.

A monitor encapsulates shared data, all the operations on the data, and any synchronization required for accessing the data. A monitor has separate constructs for mutual exclusion and condition synchronization. In fact, mutual exclusion is provided automatically by the monitor's implementation, freeing the programmer from the burden of implementing critical sections.



Synchronization - Semaphore vs. Monitor

In order to avoid data corruption and other problems, applications must control how threads access to shared resources. It is referred to as thread synchronization. The fundamental thread synchronization constructs are monitors and semaphores. Which one should we use? It depends on what the system or language supports.

  • A monitor is a set of routines that are protected by a mutual exclusion lock. A thread cannot execute any of the routines in the monitor until it acquires the lock, which means that only one thread at a time can execute within the monitor. All other threads must wait for the currently executing thread to release the lock. A thread can suspend itself in the monitor and wait for an event to occur, in which case another thread is given the chance to enter the monitor. At some point the suspended thread is notified that the event has occurred, allowing it to awake and reacquire the lock as soon as possible.

  • A semaphore is a simpler construct, just a lock that protects a shared resource. Before using a shared resource, the application must acquire the lock. Any other thread that tries to use the resource is blocked until the owning thread releases the lock, at which point one of the waiting threads acquires the lock and is unblocked. This is the most basic kind of semaphore, a mutual exclusion, or mutex, semaphore. There are other semaphore types, such as counting semaphores (which let a maximum of n threads access a resource at any given time) and event semaphores (which notify one or all waiting threads that en event has occurred), but they all work in much the same way.
    Monitors and semaphores are equivalent, but monitors are simpler to use because they handle all details of lock acquisition and release. When using semaphores, an application must be very careful to release any locks a thread has acquired when it terminates. Otherwise, no other thread that needs the shared resource can proceed. In addition, every routine that accesses the shared resource must explicitly acquire a lock before using the resource, something that is easily forgotten when coding. Monitors always and automatically acquire the necessary locks.


Thread Safe

A code is thread safe if it functions correctly in concurrent executions by multiple threads.

We can say a function is thread safe when it can safely be called from different threads simultaneously. The result is always defined if two thread safe functions are called concurrently. A class is thread safe when all of its functions can be called from different threads simultaneously without inferring with each other, even when operating on the same object.

To check if a piece of code is safe:

  1. When it accesses global variable.
  2. Alloc/realloc/freeing resources of global scope.
  3. Indirect access through handles or pointers.

To achieve a thread safety.

  1. Atomic operations - available runtime library (machine language instructions).
  2. Mutex
  3. Using Re-entrancy.


Re-entrancy

A code is re-entrant if it can be safely called again. In other words, re-entrant code can be called more than once, even though called by different threads, it still works correctly. So, the re-entrant section of code usually use local variables only in such a way that each and every call to the code gets its own unique copy of data.

  • Non-entrant code:
    int g_var = 1;
    
    int f(){
      g_var = g_var + 2;
      return g_var;
    }
    int g(){
      return f() + 2;
    }
    

    If two concurrent threads access g_var, the result depends on the time of execution of each thread.

  • Re-entrant code:
    int f(int i) { 
    return i + 2; 
    } 
    int g(int i) { 
    return f(i) + 2; 
    } 
    


Lock-Free Code

An atomic operation is one that will either successfully complete or fail. It is not possible for the operation to either result in a bad value or allow other threads on the system to have a transient value. An example of this would be an atomic increment, which would mean that the calling thread would swap n with n+1. This may look trivial, but the operation can involve several steps:

Load initial value to register
Increment the value
Store the new value back to memory

So, during the three steps, another thread could have come in and interfered, and replaced the value with a new one, creating a data race.

Typically, hardware provides support for a range of atomic operations. Atomic operations are often used to enable the writing of lock-free code. A lock-free implementation would not rely on a mutex lock to protect access. Instead, it would use a sequence of operations that would perform the operation without having to acquire an explicit lock. This can be higher performance than controlling access with a lock.



Join

A thread can execute a thread join to wait until the other thread terminates.
Let's think about the following scenario. You are preparing for tomorrow's presentation with your collegue. As a primary member of your team, you need to continue working while the other team member going out and bring a lunch for you. In this case, you are the main thread and your collegue is a child thread, and both you and your collegue are doing their job concurrently (i.e., working and bring a lunch bag). Now, we have two cases to consider. First, your collegue brings your lunch and terminates while you are working. In this case, you can stop working and enjoy the lunch. Second, you finish your work early and take a nap before the lunch is available. Of course, you cannot fall into a deep sleep; otherwise, you won't have a chance to eat the lunch. What you are going to do is to wait until your collegue brings the lunch back.

Thread join is designed to solve this problem. A thread can execute a thread join to wait until the other thread terminates. In our case, you - the main thread - should execute a thread join waiting for your collegue - a child thread - to terminate. In general, thread join is for a parent (P) to join with one of its child threads (C). Thread join has the following activities, assuming that a parent thread P wants to join with one of its child threads C:

  • When P executes a thread join in order to join with C, which is still running, P is suspended until C terminates. Once C terminates, P resumes.
  • When P executes a thread join and C has already terminated, P continues as if no such thread join has ever executed (i.e., join has no effect).

A parent thread may join with many child threads created by the parent. Or, a parent only join with some of its child threads, and ignore other child threads. In this case, those child threads that are ignored by the parent will be terminated when the parent terminates.



Context Switch

A context switch is the time spent switching between two processes (e.g., bringing a wait process into execution and sending an execution process into waiting/terminated state). This happens in multitasking. The OS must bring the state information of waiting processes into memory and save the state information of the running process.



Socket

To connect to another machine, we need a socket connection. By the way, what's a connection? A relationship between two machines, where two pieces of software know about each other. Those two pieces of software know how to communicate with each other. In other words, they know how to send bits to each other.
A socket connection means the two machines have information about each other, including network location (IP address) and TCP port.

A socket is a resource assigned to the server process.

There are several different types of socket that determine the structure of the transport layer. The most common types are stream sockets and datagram sockets.

  • Stream Sockets
    Stream sockets provide reliable two-way communication similar to when we call someone on the phone. One side initiates the connection to the other, and after the connection is established, either side can communicate to the other.
    In addition, there is immediate confirmation that what we said actually reached its destination.
    Stream sockets use a Transmission Control Protocol (TCP), which exists on the transport layer of the Open Systems Interconnection (OSI) model. The data is usually transmitted in packets. TCP is designed so that the packets of data will arrive without errors and in sequence.
    Webservers, mail servers, and their respective client applications all use TCP and stream socket to communicate.

  • Datagram Sockets
    Communicating with a datagram socket is more like mailing a letter than making a phone call. The connection is one-way only and unreliable.
    If we mail several letters, we can't be sure that they arrive in the same order, or even that they reached their destination at all. Datagram sockets use User Datagram Protocol (UDP). Actually, it's not a real connection, just a basic method for sending data from one point to another.
    Datagram sockets and UDP are commonly used in networked games and streaming media.




TCP port

A TCP port is just a number. A 16-bit number that identifies a specific program on the server.
Our internet web (HTTP) server runs on port 80. If we've got a Telnet server, it's running on port 23. FTP on 20. SMTP 25.
They represent a logical connection to a particular piece of software running on the server. Without port number, the server would have no way of knowing which application a client wanted to connect to. When we write a server program, we'll include code that tells the program which port number we want it to run on.
The TCP port numbers from 0 to 1023 are reserved for well know services:

Port Number Description
1TCP Port Service Multiplexer (TCPMUX)
5Remote Job Entry (RJE)
7ECHO
18Message Send Protocol (MSP)
20FTP -- Data
21FTP -- Control
22SSH Remote Login Protocol
23Telnet
25Simple Mail Transfer Protocol (SMTP)
29MSG ICP
37Time
42Host Name Server (Nameserv)
43WhoIs
49Login Host Protocol (Login)
53Domain Name System (DNS)
69Trivial File Transfer Protocol (TFTP)
70Gopher Services
79Finger
80HTTP
103X.400 Standard
108SNA Gateway Access Server
109POP2
110POP3
115Simple File Transfer Protocol (SFTP)
118SQL Services
119Newsgroup NNTP
137NetBIOS Name Service
139NetBIOS Datagram Service
143Interim Mail Access Protocol (IMAP)
150NetBIOS Session Service
156SQL Server
161SNMP
179Border Gateway Protocol (BGP)
190Gateway Access Control Protocol (GACP)
194Internet Relay Chat (IRC)
197Directory Location Service (DLS)
389Lightweight Directory Access Protocol (LDAP)
396Novell Netware over IP
443HTTPS
444Simple Network Paging Protocol (SNPP)
445Microsoft-DS
458Apple QuickTime
546DHCP Client
547DHCP Server
563SNEWS
569MSN
1080Socks



TCP/IP
tcpip_stack_connections

TCP/IP stack operating on two hosts connected via two routers and the corresponding layers used at each hop



Encapsulation of application data

Encapsulation of application data descending through the protocol stack.
image source wiki



TCP vs. UDP

What's the difference between TCP and UDP?

  • TCP (Transmission Control Protocol)
    TCP is a connection-oriented protocol. A connection can be made from client to server, and from then on any data can be sent along that connection.
    • Reliable
      When we send a message along a TCP socket, we know it will get there unless the connection fails completely. If it gets lost along the way, the server will re-request the lost part. This means complete integrity. In other words, the data will not get corrupted.
    • Ordered
      If we send two messages along a connection, one after the other, we know the first message will get there first. We don't have to worry about data arriving in the wrong order.
    • Heavyweight
      When the low level parts of the TCP stream arrive in the wrong order, resend requests have to be sent. All the out of sequence parts must be put back together, which requires a bit of work.

  • UDP (User Datagram Protocol)
    UDP is connectionless protocol. With UDP we send messages (packets) across the network in chunks.
    • Unreliable
      When we send a message, we don't know if it'll get there. It could get lost on the way.
    • Not ordered
      If we send two messages out, we don't know what order they'll arrive in.
    • Lightweight
      No ordering of messages, no tracking connections, etc. It's just fire and forget! This means it's a lot quicker, and the network card/OS have to do very little work to translate the data back from the packets.

transport_protocols.png



Your Ad Here


Circuit Switching vs. Packet Switching
  • Circuit Switching
    In a circuit-switched network, before communication can occur between two devices, a circuit is established between them. Once it's setup, all communication between these devices takes place over this circuit, even though there are othr possible ways between them.
  • Packet Switching
    In a packet-switched network, no circuit is set up before exchanging data between devices. Any data, even from the same file or communication, may take place any number of paths as they travel from one device to another.
    The data is broken up into packets and sent over the network. We can route, combine, or fragment the the packets as required to get them to their destination. On the receiving end, the process is reversed-the data is read from the packets and reassembled to form the original data.


Full List of C++ Tutorials