Tråde

 

 

Tråde er den måde hvorpå man kan dele et program op I flere små programmer. Lige som du på din computer kan køre flere programmer samtidig, så kan ét program også deles op i flere opgaver(programmer) som også kan køre samtidig.

Et eksempel kunne være at du har et program som henter data ind fra en fil eller database, som tager lidt tid. Hvis man ikke kunne aflever denne opgave ud i en tråd, så ville hele programmet fryse indtil indlæsningen var færdig, dvs. du kunne ikke trykke på noget på din form, imens indlæsningen foregik. Og det ville virke meget unaturligt. Endnu værre ville det være om du lavede et program som snakkede med et andet program på en helt anden computer, fordi nu måtte du vælge om du skulle lytte til indkommende beskeder eller selv sende beskeder. Det vil ligne Walky Talky hvor kun én kan snakke ad gangen, og det er ret irriterende. Når vi i dag snakker mobiltelefon, så kan begge lytte og snakke som det passer os, og det kan man fordi man har tråde, man kan altså 2 (eller flere) ting samtidig, altså både lytte og snakke.

 

Delegates:
Når vi laver en tråd, skal vi bruge en delegates. Og en delegates er bare et objekt som holder en metode. Eller sagt på en anden måde, vi sender en metode med til tråden. De nyere udgaver af C# har lavet denne del meget simplere, ved at vi ikke først skal bøvle med at lave en delegate og så sende den videre. Nu kan vi bare sende den direkte med til tråden. Lad os se hvordan den fungerer:

Forklaring

Kode

Linje 1: Der laves et objekt af typen WorkInstance med navnet w.

Linje 2: Her laves en tråd og der medsendes metoden DoMoreWork fra w objektet.

Linje 3: Her startes tråden. Tråden har nu styringen af hvornår og hvor lang tid DoMoreWork må arbejde.

WorkInstance w = new WorkInstance("Allan", 500);
Thread newThread = new Thread(w.DoMoreWork);
newThread.Start();

 


 

Kode

Udskrift

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

namespace ThreadFromMSDN

{

    class threads

    {

        static void Main()

        {

            WorkStatic.navn = "Allan";

            Thread newThread1 = new Thread(WorkStatic.DoWork);

            newThread1.Start();

 

            WorkInstance w = new WorkInstance("Berint", 500);

            Thread newThread2 = new Thread(w.DoMoreWork);

            newThread2.Start();

        }

    }

    class WorkInstance

    {

        private String navn;

        private int sleepingTime;

        public WorkInstance(String s, int i)

        {

            navn = s;

            sleepingTime = i;

        }

        public void DoMoreWork()

        {

            for (int i = 0; i < 10; i++)

            {

                Thread.Sleep(sleepingTime);

                Console.WriteLine("Instance thread. Name:{0}", navn);

            }

        }

    }

    class WorkStatic

    {

        private static int count;

        public static String navn;

        public static void DoWork()

        {

            for (int i = 0; i < 10; i++)

            {

                Thread.Sleep(1000);

   Console.WriteLine(navn + " Static thread. int: " + i + " Count: " + count++);

            }

        }

    }

}

 


 

TextBox Problemet

Opdateret okt.2011

 

Eksemplet ovenfor udskriver til consol´en. Om du skulle udskrive til f.eks. en TextBox, så kan man ikke bare gøre det. Grunden er den at TextBox´en tilhører en anden tråd, nemlig samme tråd sum hoved koden tilhører, og man kan ikke bare kalde textboxen, som man skulle tro.

Der er 2 løsninger på problemet. Den første er simpel, men lidt usikker. Den går ud på at man siger til programmet, at det IKKE skal tænke så meget på sikkerheden, og så kan vi få lov til at kalde textboxen direkte. Den anden løsning er den som man bør bruge, og som bruger Deligates.

Løsning 1 (den simple)

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Threading;

namespace ThreadForm

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        private void button1_Click_1(object sender, EventArgs e)

        {

            CheckForIllegalCrossThreadCalls = false;

            Thread thread = new Thread(new ThreadStart(TestThread));

            thread.Start();

        }

        private void TestThread()

        {

            for (int i = 0; i <= 100; i++)

            {

                Thread.Sleep(1000);

                textBox1.Text = "Tråd nr. " + i;

            }

        }

    }

}

Hvis du fjerner “CheckForIll...” så virker det ikke mere. Med denne linje så

virker det udmærke og relativt nemt at forstå.

 


 

Løsning 2 (den rigtige, lidt svær)

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Threading;

namespace ThreadForm

{

    public partial class Form1 : Form

    {

        public delegate void UpdateTextCallback(string message, int tal);

        public Form1()

        {

            InitializeComponent();

        }

        private void button1_Click(object sender, EventArgs e)

        {

            Thread thread = new Thread(new ThreadStart(TestThread));

            thread.Start();

        }

        private void UpdateText(string message, int tal)

        {

            textBox1.Text = message + tal;

        }

        private void TestThread()

        {

            for (int i = 0; i <= 100; i++)

            {

                Thread.Sleep(1000);

                textBox1.Invoke(new UpdateTextCallback(this.UpdateText), "Tråd nr. ", i);

            }

        }

    }

}

 

Som du kan se er der mere kode nu og lidt svært at gennemskue. Princippet er at

man skal bruge en delegate - som igen bare er en reference til en metode – og

sende denne til Invoke. På denne måde kan systemet som holder styr på tråde, selv

styre hvornår textbox´en bliver kaldt, og sikrer os at ingen andre er i gang med

at opdatere texten i textboxen mens vi gør det.


 

Thread Syncronization Problem in C#

 

Working with threads is difficult, and when these threads make calls to a method that i suppose to be syncronized it becomes evan more difficult. But C# has som genaral solutions to this problem, and we shall look at some of these her.

But first let os look at the problem illustrated in a drawing.
ThreadSycronizaion.jpg

The drawing showes two result a good and a bad. The bad solution has Allan beeing printet out two times befour Burt gets printet, this is not syncronized and the counter is also wrong. The good is perfekt having Allan and Burt printet in paers and the counter is also counting as it should.

But how de we get to this GOOD solution ? Lets look at some examples.


 

Example 1 (BAD):  The same example as befour  but with an extra print from the work method. This is includet to show another problem, that the work method is NOT only makin 3 calls, but many more, and this is a resource waste - handled in example 5(Efficient). We shall solve this later.


 

Example 2(GOOD): A verry simple solution is to move the printing infront of the counter and syncronize variable. This works fine but is a little dangerouse because the order of the code is important. The next example solve this ordering problmen and is therefore more safe.

ThreadSycronizaionEks2.jpg

 


 

Example 3(GOOD): Now we use Lock() and it works perfekt, and the ordering becomes not important, because everything is locked, so the other thread can not change the variables (counter, synchronizer) until we come out of the lock. This is a safer way than using IF.

ThreadSycronizaionEks3.jpg

 


 

Example 4(GOOD): A tecnique that C# gives us, is to make the entire method synchronized. The following example showes how this is done.

ThreadSycronizaionEks4.jpg

 


 

Example 5(Efficient): The last example is handling the problem that the work method are making several calls that is not used. By using Monitor.Wait and Monitor.PulseAll we are able to set an thread to wait and controlling when it is to resume the work from the exact place it was set to wait. This is complicated but a verry powerfull tool.

ThreadSycronizaionEks5.jpg


 


Code: Example 1(Bad)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

using System.Runtime.CompilerServices;

 

namespace Threads_3

{

    class Program

    {

        static void Main(string[] args) // Ikke ændre noget i Main Metoden.

        {

            ThreadTester tt = new ThreadTester();

        }

 

        class ThreadTester

        {

            public ThreadTester()

            {

                Driver driver1 = new Driver();

                driver1.myName = "Allan";

                driver1.reff = this;

                Thread thread1 = new Thread(driver1.work);

                thread1.Start();

 

                Driver driver2 = new Driver();

                driver2.myName = "Burt";

                driver2.reff = this;

                Thread thread2 = new Thread(driver2.work);

                thread2.Start();

            }

 

            int counter = 0;

            Boolean synchronizer = true;

 

            public Boolean countUp(String name)

            {

                Boolean response = true;

                if (name.Equals("Allan") && synchronizer == true)

                {

                    counter++;

                    synchronizer = false;

                    Console.WriteLine(counter + " -> succes " + name);

                }

                else if (name.Equals("Burt") && synchronizer == false)

                {

                    counter++;

                    synchronizer = true;

                    Console.WriteLine(counter + " <- succes " + name);

                }

                else

                {

                    response = false;

                }

                return response;

            }

        }

        class Driver

        {

            public ThreadTester reff;

            public String myName;

            public void work()

            {

                for (int i = 0; i < 3; i++)

                {

                    Console.WriteLine("   work() has fired  " + myName);

                    Boolean svar = reff.countUp(myName);

                    if (svar == false)

                    {

                        i--;

                    }

                }

            }

        }

    }

}

 


 

 

Code: Example 5(efficient)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

using System.Runtime.CompilerServices;

 

namespace Threads_3

{

    class Program

    {

        static void Main(string[] args) // Ikke ændre noget i Main Metoden.

        {

            ThreadTester tt = new ThreadTester();

        }

 

        class ThreadTester

        {

            public ThreadTester()

            {

                Driver driver1 = new Driver();

                driver1.myName = "Allan";

                driver1.reff = this;

                Thread thread1 = new Thread(driver1.work);

                thread1.Start();

 

                Driver driver2 = new Driver();

                driver2.myName = "Burt";

                driver2.reff = this;

                Thread thread2 = new Thread(driver2.work);

                thread2.Start();

            }

 

 

            public Boolean countUp(String name)

            {

                if (name.Equals("Allan"))

                {

                    Allan();

                }

                else if(name.Equals("Burt"))

                {

                    Burt();

                }

                return true;

            }

 

            int counter = 0;

            Boolean synchronizer = true;

 

            private void Allan()

            {

                lock (this)

                {

                    if (synchronizer == false)

                    {

                        Console.WriteLine("--- Allan waits ---");

                        Monitor.Wait(this);

                    }

                    if (synchronizer == true)

                    {

                        synchronizer = false;

                        counter++;

                        Console.WriteLine(counter + " <- succes Allan");

                        Monitor.PulseAll(this);

                    }

                }

            }

            private void Burt()

            {

                lock (this)

                {

                    if (synchronizer == true)

                    {

                        Console.WriteLine("--- Burt waits ---");

                        Monitor.Wait(this);                       

                    }

                    if (synchronizer == false)

                    {

                        synchronizer = true;

                        counter++;

                        Console.WriteLine(counter + " <- succes Burt");

                        Monitor.PulseAll(this);

                    }

                }

            }

        }

        class Driver

        {

            public ThreadTester reff;

            public String myName;

            public void work()

            {

                for (int i = 0; i < 3; i++)

                {

                    Console.WriteLine("   work() has fired  " + myName);

                    Boolean svar = reff.countUp(myName);

                    if (svar == false)

                    {

                        i--;

                    }

                }

            }

        }

    }

}

 


 

Producer/Consumer

Producer/Consumer Simulation

De eksempler vi har undersøgt ovenover viser ret godt hvad tråde kan og hvad man skal passe på. Men indenfor Computer verdenen har man nogle standard scenarier som man bruger som ståsted når man diskuterer tråde.  Producer/Consumer er en af disse.

Producer-consumer problem

From Wikipedia, the free encyclopedia

The producer-consumer problem (also known as the bounded-buffer problem) is a classic example of a multi-process synchronizationproblem. The problem describes two processes, the producer and the consumer, who share a common, fixed-size buffer used as a queue. The producer's job is to generate a piece of data, put it into the buffer and start again. At the same time, the consumer is consuming the data (i.e., removing it from the buffer) one piece at a time. The problem is to make sure that the producer won't try to add data into the buffer if it's full and that the consumer won't try to remove data from an empty buffer. [1]

The solution for the producer is to either go to sleep or discard data if the buffer is full. The next time the consumer removes an item from the buffer, it notifies the producer, who starts to fill the buffer again. In the same way, the consumer can go to sleep if it finds the buffer to be empty. The next time the producer puts data into the buffer, it wakes up the sleeping consumer. The solution can be reached by means of inter-process communication, typically using semaphores. An inadequate solution could result in a deadlock where both processes are waiting to be awakened. The problem can also be generalized to have multiple producers and consumers.

 

 

Producer/Consumer

(uden Wait/Puls)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

namespace ConsoleApplication45

{

    class Program

    {

        static void Main(string[] args)

        {

            Buffer buffer = new Buffer();

            Test t1 = new Test("Producer", buffer);

            Test t2 = new Test("Consumer", buffer);

            Thread tråd1 = new Thread(t1.kør);

            Thread tråd2 = new Thread(t2.kør);

            tråd1.Start();

            tråd2.Start();

        }

    }

    class Buffer

    {

        int teller = 0;

        public void telOp(String navn)

        {

            lock (this)

            {

                if (navn.Equals("Producer"))

                {

                    if (teller < 3)

                    {

                        teller = teller + 1;

                        Console.WriteLine("Producer har produceret: " + teller);

                    }

                    else

                    {

                        Console.WriteLine("Producer fik ikke lov til at producere: " + teller);

                    }

                }

                else if (navn.Equals("Consumer"))

                {

                    if (teller > 0)

                    {

                        teller = teller - 1;

                        Console.WriteLine("Consumer har consumeret: " + teller);

                    }

                    else

                    {

                        Console.WriteLine("Consumer fik ikke lov til at consumere: " + teller);

                    }

                }

            }

        }

    }

    class Test

    {

        private String navn;

        private Buffer buf;

        public Test(string n, Buffer b)

        {

            navn = n;

            buf = b;

        }

        public void kør()

        {

            int i = 0;

            while (i < 10)

            {

                buf.telOp(navn);

                i = i + 1;

            }

        }

    }

}

 

 

https://lh5.googleusercontent.com/VE5Vz8IdjE41PliZEfmxylpQRjFDB7_gZ9hZXj93xKMZ-TXh9cxlq26r9KYk8s2FhFezoMJhVFlFR3OJD3rR86Ewx2zE7maLooFE6AT85l8BEeXjv44KkSdG--Ax9QwLpQ

Det vigtige her er at både consumer og producer laver mange kald som går til spilde. De spørger flere gange om de må producere eller consume, men får negativt svar, men prøver bare igen lidt efter. Dette er spild af resourcer.

 

 

 

Producer/Consumer

(Med Wait og Puls)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

namespace ConsoleApplication45

{

    class Program

    {

        static void Main(string[] args)

        {

            Buffer buffer = new Buffer();

            Test t1 = new Test("Producer", buffer);

            Test t2 = new Test("Consumer", buffer);

            Thread tråd1 = new Thread(t1.kør);

            Thread tråd2 = new Thread(t2.kør);

            tråd1.Start();

            tråd2.Start();

        }

    }

    class Buffer

    {

        int teller = 0;

        public void telOp(String navn)

        {

            lock (this)

            {

                if (navn.Equals("Producer"))

                {

                    while (teller > 2)

                    {

                        Console.WriteLine("Producer waits...");

                        Monitor.Wait(this);

                    }

                    teller = teller + 1;

                    Console.WriteLine("Producer har produceret: " + teller);

                    Monitor.Pulse(this);

                }

                else if (navn.Equals("Consumer"))

                {

                    while (teller < 1)

                    {

                        Console.WriteLine("Consumer waits...");

                        Monitor.Wait(this);

                    }

                    teller = teller - 1;

                    Console.WriteLine("Consumer har consumeret: " + teller);

                    Monitor.Pulse(this);

                }

            }

        }

    }

 

    class Test

    {

        private String navn;

        private Buffer buf;

        public Test(string n, Buffer b)

        {

            navn = n;

            buf = b;

        }

        public void kør()

        {

            int i = 0;

            while (i < 10)

            {

                buf.telOp(navn);

                i = i + 1;

            }

        }

    }

}

Nu er der intet spild af resourcer. Når produceren eller consumeren får negativt svar, så venter de bare indtil de må komme til.

 

Dog skal man være forsigtig med koden her, fordi man kan komme i en deadlock hvis nu begge går i Wait tilstanden, fordi så er der ingen til at vække nogen op, og programmet stopper/deadlock.

 


 

Dining philosophers

 

Dining philosophers Simulation

 

Dette eksempel fra Computer verdenen ligner en del Producer/Consumer men er lidt mere indviklet. Fordi nu har vi 5 bufferer (gaffler). Det som er specielt godt med eksemplet er at det er ret nemt at komme i en Deadlock hvor alle venter på hinanden.

Dining philosophers problem

From Wikipedia, the free encyclopedia

In computer science, the dining philosophers problem is an example problem often used in concurrent algorithm design to illustrate synchronization issues and techniques for resolving them.

It was originally formulated in 1965 by Edsger Dijkstra as a student exam exercise, presented in terms of computers competing for access to tape drive peripherals. Soon after, Tony Hoare gave the problem its present formulation.[1][2][3]

 

Problem statement

File:Dining philosophers.png

Illustration of the dining philosophers problem

 

 

Five silent philosophers sit at a table around a bowl of spaghetti. A fork is placed between each pair of adjacent philosophers.

Each philosopher must alternately think and eat. However, a philosopher can only eat spaghetti when he has both left and right forks. Each fork can be held by only one philosopher and so a philosopher can use the fork only if it's not being used by another philosopher. After he finishes eating, he needs to put down both the forks so they become available to others. A philosopher can grab the fork on his right or the one on his left as they become available, but can't start eating before getting both of them.

Eating is not limited by the amount of spaghetti left: assume an infinite supply. An alternative problem formulation uses rice and chopsticks instead of spaghetti and forks.

 

The problem is how to design a discipline of behavior (a concurrent algorithm) such that each philosopher won't starve, i.e. can forever continue to alternate between eating and thinking, assuming that any philosopher cannot know when others may want to eat or think.

 

Issues

The problem was designed to illustrate the problem of avoiding deadlock, a system state in which no progress is possible.

One idea is to instruct each philosopher to behave as follows:

think until the left fork is available and when it is pick it up

think until the right fork is available and when it is pick it up

eat for a fixed amount of time

put the right fork down

put the left fork down

repeat from the beginning.

 

This attempt at a solution fails: It allows the system to reach a deadlock state in which each philosopher has picked up the fork to the left, waiting for the fork to the right to be put down—which never happens, because A) each right fork is another philosopher's left fork, and no philosopher will put down that fork until they eat, and B) no philosopher can eat until they acquire the fork to their own right, which has alreadybeen picked up by the philosopher to their right as described above.[4]

Resource starvation might also occur independently of deadlock if a particular 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 ten minutes for the other fork to become available and wait a further ten 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 ten minutes until they all put their forks down and then wait a further ten minutes before they all pick them up again.

Mutual exclusion is the core idea of the problem; the dining philosophers create a generic and abstract scenario useful for explaining issues of this type. The 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 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.

 

 

Fagudtryk (Tråde)

Lock

Bruges til at låse adgangen til en ressource. Er aktuelt når der er flere tråde som ønsker adgang til denne. 

Lock er en nemmere måde end Monitor.Enter og Monitor.Exit.

Lock(this){  }

Monitor.Enter

Monitor.Exit

Bruges til at låse adgangen til en ressource. Er aktuelt når der er flere tråde som ønsker adgang til denne

Monitor.Enter og Monitor.Exit bruges lige som lock, men her må vi selv lave try og final, hvis vi skal have det samme som vi får med Lock. Se eksemplet på forskellen under (Threading with Monitor in C#).

Monitor.Wait

Monitor.Puls

Monitor.PulsAll

Bruges inden i en LÅST ressource, dvs når vi har brugt Lock eller Monitor.Wait og Puls.

Monitor.Wait og Monitor.PulsAll bruges til at Vent til den aktuel tråd får adgang til resources, og når denne er færdig må han vække alle de andre tråde som venter på at komme til.

Wait sætter aktuel tråd-som er inden i en lock – til at vente, således at en anden tråd kan komme ind i resourcen (samtidig med at vi er inde, men i vente position). Når så denne anden tråd er færdig med sit, vækkes den aktuelle tråd igen og kan køre videre fra der den stoppede inden i resourcen…lidt mærkeligt, men smart.

Eks:
Lock(this)
{
    ……
     Monitor.Wait(this);
     …….
     Monitor.Puls(this);
     ……..
}

Semaphore

Semaphore (programming)

From Wikipedia, the free encyclopedia

In computer science, a semaphore is a variable or abstract data type that provides a simple but useful abstraction for controlling access by multiple processes to a common resource in a parallel programming or multi user environment.

A useful way to think of a semaphore is as a record of how many units of a particular resource are available, coupled with operations to safely(i.e., without race conditions) adjust that record as units are required or become free, and, if necessary, wait until a unit of the resource becomes available. Semaphores are a useful tool in the prevention of race conditions; however, their use is by no means a guarantee that a program is free from these problems. Semaphores which allow an arbitrary resource count are called counting semaphores, while semaphores which are restricted to the values 0 and 1 (or locked/unlocked, unavailable/available) are called binary semaphores (same functionality that mutexes have).

The semaphore concept was invented by Dutch computer scientist Edsger Dijkstra in 1965,[1] and the concept has found widespread use in a variety of operating systems.

Race condition

 

                           Race condition

From Wikipedia, the free encyclopedia

 A race condition or race hazard is a type of flaw in an electronic or software systemwhere the output is dependent on the sequence or timing of other uncontrollable events. The term originates with the idea of two signals racing each other to influence the outputfirst.

Race conditions can occur in electronics systems, especially logic circuits, and incomputer software, especially multithreaded or distributed programs.

 

Software

Race conditions arise in software when separate processes or threads of execution depend on some shared state. Operations upon shared states are critical sections that must be mutually exclusive. Failure to do so opens up the possibility of corrupting the shared state.

Race conditions are notoriously difficult to reproduce and debug, since the end result is nondeterministic, and highly dependent on the relative timing between interfering threads. Problems occurring in production systems can therefore disappear when running in debug mode, when additional logging is added, or when attaching a debugger, often referred to as a Heisenbug. It is therefore highly preferable to avoid race conditions in the first place by careful software design than to fix problems afterwards.

Mutually exclusive:Two events are 'mutually exclusive' if they cannot occur at the same time. An example is tossing a coin once, which can result in either heads or tails, but not both.