<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jaideep more</title>
    <description>The latest articles on DEV Community by Jaideep more (@jaideepmore).</description>
    <link>https://dev.to/jaideepmore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1116751%2Fd42a6273-f252-400e-8114-8613063232cb.jpeg</url>
      <title>DEV Community: Jaideep more</title>
      <link>https://dev.to/jaideepmore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jaideepmore"/>
    <language>en</language>
    <item>
      <title>Memory Coherence in Shared Virtual Memory Systems</title>
      <dc:creator>Jaideep more</dc:creator>
      <pubDate>Mon, 17 Jul 2023 10:24:52 +0000</pubDate>
      <link>https://dev.to/jaideepmore/memory-coherence-in-shared-virtual-memory-systems-1c2p</link>
      <guid>https://dev.to/jaideepmore/memory-coherence-in-shared-virtual-memory-systems-1c2p</guid>
      <description>&lt;p&gt;The article serves as a summary of the paper - &lt;a href="https://courses.mpi-sws.org/ds-ws18/papers/li-dsm.pdf"&gt;Memory Coherence in Shared Virtual Memory Systems&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Choices for Memory Coherence:
&lt;/h2&gt;

&lt;p&gt;Our design goals require that the shared virtual memory is coherent. Coherence can be maintained if a shared virtual memory satisfies the following single constraint:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;A processor is allowed to update a piece of data only while no other processor is updating or reading it&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two design choices greatly influence the implementation of a shared virtual memory, granularity and strategy for maintaining coherence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Granularity:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Memory Contention:&lt;/strong&gt; When two or more threads try to access the same memory location simultaneously, contention occurs, and one or more threads may be blocked or delayed in their execution. This can lead to performance degradation and even deadlock in some cases.&lt;/p&gt;

&lt;p&gt;The possibility of contention pushes one toward relatively small memory units.&lt;/p&gt;

&lt;p&gt;A suitable granular compromise is a typical page used in a conventional virtual memory implementation. Part of the justification for using page size granularity is that memory references in sequential programs generally have a high degree of locality.&lt;/p&gt;

&lt;p&gt;Although memory references in parallel programs may behave differently from those in sequential ones, a single process remains a sequential program and should exhibit a high degree of locality.&lt;/p&gt;

&lt;p&gt;In addition, such a choice allows us to use existing page-fault schemes. This can be done by setting the access rights to the pages so that memory accesses that could violate memory coherence cause a page fault. Thus the memory coherence problem can be solved in a modular way in the page fault handlers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Coherence Strategies:
&lt;/h3&gt;

&lt;p&gt;The strategies may be classified by how one deals with page synchronisation and ownership.&lt;/p&gt;

&lt;h4&gt;
  
  
  Page Synchronisation:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Invalidation:&lt;/strong&gt; If a processor has a write fault, the fault handler will copy the true page and invalidate all other copies of the page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write Back:&lt;/strong&gt; For a write fault, the fault handler will write to all the copies of the page. Clearly, this is expensive as all write operations will cause a write-back. Not suitable for loosely coupled processors.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Page Ownership:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static:&lt;/strong&gt; The same processor always owns a page. This means that other processors request the owner process for any updates to the page. Generates write faults for every write operation. Not suitable for our system&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Centralised Managers&lt;/li&gt;
&lt;li&gt;Distributed Managers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solutions for Memory Coherence Problems:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AkJUTDbf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pk5ygs7p2al0hbanvod6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AkJUTDbf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pk5ygs7p2al0hbanvod6.png" alt="Strategy comparison" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved Centralised Manager Algorithms:
&lt;/h2&gt;

&lt;p&gt;The responsibility for the synchronisation of pages is given to the individual processors/owners.&lt;/p&gt;

&lt;p&gt;Each processor maintains data structure &lt;code&gt;ptable&lt;/code&gt; such that for every page, it stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;access&lt;/code&gt;: type of access.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;copy_set&lt;/code&gt;: list of processor ids that have copies of the&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lock&lt;/code&gt;: for synchronising the request to the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A manager stores only the page ownership information in the &lt;code&gt;owner&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;The following diagrams provide the flowchart of the fault handlers and their corresponding service.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fault handlers&lt;/em&gt; are functions invoked when a read/write operation is performed on a page the processor does not own.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Services&lt;/em&gt; are functions that are invoked when a page owner receives a read/write request from the manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ua-QoprA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zgxhr6bkyg6iq4665pfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ua-QoprA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zgxhr6bkyg6iq4665pfs.png" alt="Flowchart for Centralised manager" width="800" height="795"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Manager Algorithms:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fixed Distributed Manager Algorithm:
&lt;/h3&gt;

&lt;p&gt;Every processor is given a predetermined subset of the pages to manage. The most straightforward approach is to distribute the pages evenly in a fixed manner to all processors.&lt;/p&gt;

&lt;p&gt;When a fault occurs on a page &lt;code&gt;p&lt;/code&gt;, the faulting processor asks &lt;code&gt;H(p)&lt;/code&gt; where the true page owner is and proceeds as in the centralised manager algorithm.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It is, however, difficult to find a good static distribution that fits all applications well.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Broadcast Distributed Manager Algorithm:
&lt;/h3&gt;

&lt;p&gt;An obvious way of eliminating the centralised manager is by using broadcast mechanisms. With this strategy, each processor manages precisely those pages it owns, and faulting processors send broadcasts into the network to find the page's owner.&lt;/p&gt;

&lt;p&gt;Thus the &lt;code&gt;owner&lt;/code&gt; table is eliminated, and the ownership information is stored in each processor's &lt;code&gt;ptable&lt;/code&gt;, which in addition to &lt;code&gt;access&lt;/code&gt;, &lt;code&gt;copy_set&lt;/code&gt; and &lt;code&gt;lock&lt;/code&gt; fields also has an &lt;code&gt;owner&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;More precisely, when a read fault occurs, the faulting processor &lt;code&gt;P&lt;/code&gt; sends a broadcast read request, and the true owner of the page responds by adding &lt;code&gt;P&lt;/code&gt; to the page's &lt;code&gt;copy_set&lt;/code&gt; field and sending a copy of the page to &lt;code&gt;P&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similarly, when a write fault occurs, the faulting processor sends a broadcast write request, and the true owner of the page gives up ownership and sends back the page and its &lt;code&gt;copy_set&lt;/code&gt;. When the requesting processor receives the page and the &lt;code&gt;copy_set&lt;/code&gt;, it will invalidate all copies.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Broadcasting every time a page fault occurs leads to the communication system being a potential bottleneck.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Distributed Manager Algorithm:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pIZ9-6u---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dh1uhszx0v72sky1dis5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pIZ9-6u---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dh1uhszx0v72sky1dis5.png" alt="Flowchart for Distributed manager" width="800" height="782"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The heart of a dynamic distributed manager algorithm is to attempt to keep track of the ownership of all pages in each processor's local &lt;code&gt;ptable&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;To do this, the &lt;code&gt;owner&lt;/code&gt; field is replaced with another field, &lt;code&gt;prob_owner&lt;/code&gt;, whose value can be either &lt;code&gt;null&lt;/code&gt; or the "probable" owner of the page.&lt;/p&gt;

&lt;p&gt;The information that &lt;code&gt;prob_owner&lt;/code&gt; contains is not necessarily correct at all times, but if incorrect, it will at least provide the beginning of a sequence of processors through which the true owner can be found.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Programming with Threads</title>
      <dc:creator>Jaideep more</dc:creator>
      <pubDate>Mon, 10 Jul 2023 09:27:43 +0000</pubDate>
      <link>https://dev.to/jaideepmore/programming-with-threads-1df8</link>
      <guid>https://dev.to/jaideepmore/programming-with-threads-1df8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article serves as a summary for the paper - &lt;a href="https://courses.mpi-sws.org/ds-ws18/papers/birrell-threads-csharp.pdf"&gt;Programming with Threads&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;A "thread" is a straightforward concept: a single sequential flow of control. Having multiple threads in a program means that at any instant the program has multiple points of execution. &lt;/p&gt;

&lt;p&gt;The programmer can mostly view the threads as executing simultaneously, as if the computer were endowed with as many processors as there are threads. The programmer must be aware that the computer might not in fact execute all his threads simultaneously.&lt;/p&gt;

&lt;p&gt;Each thread executes on a separate call stack with its own separate local variables while the off-stack (global) variables being shared among all the threads of the program. The programmer is responsible for using appropriate synchronisation mechanisms to ensure that the shared memory is accessed in a manner that will give the correct answer.&lt;/p&gt;

&lt;p&gt;A thread facility allows us to write programs with multiple simultaneous points of execution, synchronising through shared memory. In this article we discuss the basic thread and synchronisation primitives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use Concurrency?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Use of multiple processors (Obvious)&lt;/li&gt;
&lt;li&gt;Driving slow devices such as disks or networks. In these case an efficient program should be doing some other useful tasks while waiting for device to produce its next event.&lt;/li&gt;
&lt;li&gt;A third source of concurrency is human users. When your program is performing some lengthy task for the user, the program should still be responsive.&lt;/li&gt;
&lt;li&gt;We can deliberately add concurrency to our program in order to reduce the latency of operations. For example, some of the work incurred by a method call can be deferred if it does not affect the result of the call. For example, when you add or remove something in a balanced tree you could happily return to the caller before re‐balancing the tree. Re-balancing done in a separate thread.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Design of thread facility:
&lt;/h2&gt;

&lt;p&gt;In general there are four major mechanisms:&lt;/p&gt;

&lt;h3&gt;
  
  
  Thread Creation:
&lt;/h3&gt;

&lt;p&gt;Creating and starting a thread is called “forking”. Most forked threads are daemon threads&lt;/p&gt;

&lt;h3&gt;
  
  
  Mutual Exclusion:
&lt;/h3&gt;

&lt;p&gt;Threads interact through access to shared memory. To avoid errors arising when more than one thread is accessing the shared variable a mutual exclusion tool is used. It specifies a particular region of code that only one thread can execute at any time.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;lock&lt;/code&gt; statement locks the given object, then executes the contained statements, then unlocks the object.&lt;/p&gt;

&lt;p&gt;A thread executing inside the &lt;code&gt;lock&lt;/code&gt; statement is said to “hold” the given object’s lock. If another thread attempts to lock the object when it is already locked, the second thread blocks (enqueued on the object’s lock) until the object is unlocked.&lt;/p&gt;

&lt;p&gt;In general we achieve mutual exclusion on a set of variables by associating them (mentally) with a particular object. We can then write our program so that it accesses those variables only from a thread which holds that objects lock.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting for Events:
&lt;/h3&gt;

&lt;p&gt;Often programmer needs to express more complicated scheduling policies. This requires use of a mechanism that allows a thread to block itself until some condition is true.&lt;/p&gt;

&lt;p&gt;The mechanism used to achieve this is generally called "condition variables" which provides functionalities to allow programmers to express complicated scheduling policies. These functions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Wait(object)&lt;/code&gt;: atomically unlocks the object and blocks the thread.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Pulse(object)&lt;/code&gt;: awakens a thread that is waiting on that object&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PulseAll(object)&lt;/code&gt;: awakens all threads that are waiting on that object&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Interrupt:
&lt;/h3&gt;

&lt;p&gt;The final mechanism is for interrupting a particular thread. if &lt;code&gt;threadA&lt;/code&gt; is blocked waiting for a condition, and &lt;code&gt;threadB&lt;/code&gt; calls &lt;code&gt;threadA.interrupt()&lt;/code&gt;, then &lt;code&gt;threadA&lt;/code&gt; will resume execution by re-locking the object and throwing an &lt;code&gt;InterruptException&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Using Locks: Accessing Shared Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unprotected Data:
&lt;/h3&gt;

&lt;p&gt;The simplest bug related to locks occurs when we fail to protect some mutable data and then we access it without synchronisation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;lock&lt;/code&gt; statement enforces serialisation of threads, so that at any time only one thread executes the statements inside the &lt;code&gt;lock&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invariants:
&lt;/h3&gt;

&lt;p&gt;Programmers find it easier to think of the lock as protecting the invariant of the associated data. &lt;/p&gt;

&lt;p&gt;An invariant is a boolean function which checks the constraints the object must satisfy at any given time. Invariant must be true whenever the associated lock is not held.&lt;/p&gt;

&lt;p&gt;Releasing the lock while our state is in a transient inconsistent state will inevitably lead to confusion if it is possible for another thread to acquire lock in this state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deadlocks involving only locks:
&lt;/h3&gt;

&lt;p&gt;The most effective rule for avoiding deadlocks is to have a partial order for the acquisition of locks in our programs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Poor performance through lock conflicts:
&lt;/h3&gt;

&lt;p&gt;Whenever a thread is holding a lock, it is preventing another thread from making progress.&lt;/p&gt;

&lt;p&gt;The best way to reduce lock conflicts is to lock at a finer granularity, which introduces complexity. It is a trade-off inherent in concurrent computation.&lt;/p&gt;

&lt;p&gt;The most typical example where locking granularity is important is in a class that manages a set of object, for example a set of open buffered files. &lt;/p&gt;

&lt;p&gt;The simplest strategy is to use a single global lock for all the operations. But this would prevent simultaneous operations of two different files. The most obvious way to use the locks is to have one global lock that protects the global data structures of the class and have object specific locks which protect the data specific to that instance.&lt;/p&gt;

&lt;p&gt;There is an interaction between locks and the thread scheduler that can produce insidious performance problem. Thread scheduler decides which  of the non-blocked threads should be given a processor to run on. Generally, this decision is based on a priority associated with each thread. &lt;/p&gt;

&lt;p&gt;Lock conflicts can lead to priority inversion in which a thread even with the highest priority fails to make progress. Ex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C is running (e.g., because A and B are blocked somewhere); 
C locks object M;  
B wakes up and pre-empts C(i.e., B runs instead of C since B has higher priority); 
B embarks on some very long computation;  
A wakes up and pre-empts B (since A has higher priority); 
A tries to lock M, but can’t because it’s still locked by C;  
A blocks, and so the processor is given back to B;  
B continues its very long computation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best solution to this problem lies in the thread scheduler. Ideally, the scheduler should raise threads C's priority which thats needed to enable thread A to eventually make progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait and Pulse:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scheduling shared Resources:
&lt;/h3&gt;

&lt;p&gt;When we want to schedule &lt;strong&gt;the way in which multiple threads access some shared resource&lt;/strong&gt;, then we want to make threads block by waiting on an object. Simple mutual exclusion is not sufficient in such cases.&lt;/p&gt;

&lt;p&gt;Use the following general pattern, which is strongly recommended for all uses of condition variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while(!expression) Monitor.wait(object);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main reason for advocating use of this pattern is to make your program more obviously, and more robustly, correct. With this style it is immediately clear that the expression is true before the following statements are executed&lt;/p&gt;

&lt;p&gt;This programming convention allows us to verify correctness by local inspection, which is always preferred over global inspection (looking for all places where &lt;code&gt;pulse(object)&lt;/code&gt; is called).&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;PulseAll()&lt;/code&gt;:
&lt;/h3&gt;

&lt;p&gt;A simple example where &lt;code&gt;PulseAll()&lt;/code&gt; is useful in when we want to awaken multiple threads, because the resource we have just made available can be used by several other threads.&lt;/p&gt;

&lt;p&gt;One use of &lt;code&gt;PulseAll()&lt;/code&gt; is when you want to simplify your program by awakening multiple threads, even though you know that not all of them can make progress.&lt;/p&gt;

&lt;p&gt;If we always program in the recommended style mentioned above then the correctness of our program will be unaffected if we replace all &lt;code&gt;Pulse&lt;/code&gt; with &lt;code&gt;PulseAll&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This use trades slightly poorer performance for greater simplicity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spurious Lock Conflicts:
&lt;/h3&gt;

&lt;p&gt;A potential source of excessive scheduling overhead comes from situations where a thread is awakened from waiting on an object, and before doing useful work the thread blocks trying to lock an object.&lt;/p&gt;

&lt;p&gt;Example: A thread awakens another thread by using &lt;code&gt;Pulse()&lt;/code&gt; but is yet to release the lock that will block the awakened thread. This has cost us two extra reschedule operations, which is a significant expense.&lt;/p&gt;

&lt;p&gt;If getting the best performance is important, we need to consider carefully whether a newly awakened thread will necessarily block on some other object shortly after it starts running. If so we need to arrange to defer the wake-up to a more suitable time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starvation:
&lt;/h3&gt;

&lt;p&gt;Whenever we have a program that is making scheduling decisions, we must worry about how fair these decisions are; in other words, are all threads equal or are some more favoured?&lt;/p&gt;

&lt;p&gt;When you are locking an object, this consideration is dealt with for you by the threads implementation typically by a first‐in‐first‐out rule for each priority level.&lt;/p&gt;

&lt;p&gt;The most extreme form of unfairness is “starvation”, where some thread will never make progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluding Remarks
&lt;/h2&gt;

&lt;p&gt;A successful program must be useful, correct, live and efficient. Our use of concurrency can impact each of these. We have discussed quite a few techniques in the previous sections that will help us in achieving these goals.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>concurrency</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
