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.
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.
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.
Example 4(GOOD): A tecnique that C# gives us, is to make the
entire method synchronized. The following example showes how this is done.
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.
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;
}
}
}
}
|
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
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.
|
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.
|
|