# Sunday, June 08, 2008

Multithreading - Teil 1 - das lock-Statement

Bereits seit einiger Zeit sind Multi-Core-Prozessoren für den normalen Konsumenten erschwinglich. In Servern werden bereits seit einigen Jahren Multiprozessorsysteme eingesetzt. Jedoch kommt mir immer wieder Code unter, der sich strikt an einen Kern bindet, weil keine Operation asynchron ausgeführt wird. Ich persönlich vertrete die Meinung, dass  allein aus Gründen der User Experience mehrere Threads eingesetzt werden sollen. Welcher Benutzer sieht es schon gern, wenn er auf einen Button drückt, und die Anwendung für einige Sekunden zu einer weißen Fläche wird? In Gesprächen mit Entwicklern höre ich jedoch immer wieder das Argument, dass Multithread-Programmierung ja so kompliziert sei und man es lieber nicht verwendet, um keine Fehler zu machen. Denn das Debugging von Multithreaded-Code ist nicht so trivial wie bei Singlethreaded-Code. Daher möchte ich in einer unbestimmt langen Reihe einige Aspekte des Multithreading beleuchten.

lock

Beginnen möchte ich mit dem C#-Keyword lock. Das lock-Statement ist nur in C# verfügbar. Es wird ähnlich dem using-Statement verwendet:

lock(obj)
{
//Do something synchronized }

lock wird also auf ein Objekt aufgerufen. Der Code innerhalb des Blocks wird nur aufgeführt, wenn das der lock-Aufruf zurückgekehrt ist. Es ist also sichergestellt, dass der Code innerhalb des Blocks immer nur von einem Thread ausgeführt wird. Der Blockinhalt wird auch Critical Section genannt.

Wie verwendet man lock?

Die Syntax für die Nutzung von lock ist denkbar einfach. Allerdings sind einige Einschränkungen bei der Verwendung zu beachten.

Reference-Type

lock kann nur auf Reference-Types aufgerufen werden. Dies hängt damit zusammen, dass lock eigentlich eine Maskierung für

Monitor.Enter(lockobject);

ist.

Auf welches Objekt soll das lock aufgerufen werden?

Oft sieht man Konstrukte wie

lock(this){ }

Diese verletzen jedoch oft die Design Guidelines für Multithreadprogrammierung. Und zwar in den folgenden Fällen:

  • lock(this){ } kann zu einem Problem werden, wenn die Instanz des Objekts public ist, also öffentliche Zugriffe erlaubt.

  • lock(typeof(MyType)){ } ist ebenfalls problematisch, wenn MyType öffentlich zugänglich ist.

  • lock("mylock") { } bedeutet, dass alle lock-Statements, die so geschrieben werden,
    dasselbe Lock-Objekt benutzen, da es sich um einen String handelt.

lock sollte also immer auf dedizierte private Objekte aufgerufen werden. Soll eine Critical Section über alle Instanzen eines Objekts geschützt sein, definiert man das lock-Objekt als static.

private static object lockObj = new object();

private void foo()
{
lock(lockObj)
{
//Critical section } }

Was passiert bei einem lock?

Nehmen wir eine Methode einer Windows-Forms-Anwendung. Auf dem Formular ist ein Button button1 platziert. Klickt man diesen an, soll sich der Text des Formulars nach „LOCKED“ ändern. Es ergibt sich folgende Funktion:

private void button1_Click(object sender, EventArgs e)
{
lock (this.lockObject)
{
this.Text = "LOCKED";
}
}

Was macht der Compiler aus dieser Funktion? Schauen wir uns die erzeugte Intermediate Language an:

.method private hidebysig instance void button1_Click(object sender, class [mscorlib]System.EventArgs e) cil managed
{
.maxstack 2
.locals init (
[0] object CS$2$0000)
L_0000: nop
L_0001: ldarg.0
L_0002: ldfld object lock1.Form1::lockObject
L_0007: dup
L_0008: stloc.0
L_0009: call void [mscorlib]System.Threading.Monitor::Enter(object)
L_000e: nop
L_000f: nop
L_0010: ldarg.0
L_0011: ldstr "LOCKED"
L_0016: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
L_001b: nop
L_001c: nop
L_001d: leave.s L_0027
L_001f: ldloc.0
L_0020: call void [mscorlib]System.Threading.Monitor::Exit(object)
L_0025: nop
L_0026: endfinally
L_0027: nop
L_0028: ret
.try L_000f to L_001f finally handler L_001f to L_0027
}

Wie man sieht, wird das lock-Statement zu einem System.Threading.Monitor.Enter(lockObject) aufgelöst. Der entsprechende Aufruf der Methode System.Threading.Monitor.Exit(lockObject) wird in einen finally-Block eingefasst. Es kann also innerhalb der Critical section passieren, was will, der Monitor wird  freigegeben. Dies ist einer der großen Vorteile des lock-Statements. Der Entwickler muss sich nur sehr wenige Gedanken um die korrekte Freigabe des Monitors machen, da dies der Compiler für ihn erledigt.

Allerdings verbirgt sich hier auch einer der Nachteile des lock-Statements. Der Compiler löst das lock-Statement in ein Monitor.Enter(lockObject) auf. Das kann unter Umständen dazu führen, dass der Aufruf nie zurückkehrt, man also in eine Deadlock-Situation gerät. Um dies zu vermeiden, verwendet man besser Monitor.TryEnter(object, TimeSpan). TryEnter versucht für die angegebene Zeitspanne ein lock auf das Objekt zu erlangen. Schlägt dies fehl, kehrt die Methode mit false zurück. Der Nachteil dieses Aufrufs ist jedoch, dass der Entwickler sich nun selbst um das Fehlerhandling kümmern muss. Folgender Codeblock kann also statt des lock-Statements verwendet werden:

if ( !Monitor.TryEnter( lock2, TimeSpan.FromSeconds( 1 ) ) )
{
    throw new ApplicationException( "Unable to claim lock on object {0}", lock2.ToString() );
}
try
{
    //Critical section
}
finally
{
    Monitor.Exit( lock2 );
}

In diesem Fall liegt die Freigabe des Monitors jedoch ganz auf Seiten des Entwicklers. Außerdem muss er sich Gedanken darüber machen, wie die Applikation darauf reagiert, wenn der Monitor nicht in der vorgegebenen Zeitspanne erlangt werden kann. Deadlocks sind so jedoch ausgeschlossen.

Einige Beispiele

Zuerst schauen wir uns an, wie sich zwei Methoden verhalten, die von unterschiedlichen Threads aus aufgerufen werden. Eine dieser Methoden ist durch ein lock verriegelt:

class Program
{
static void Main( string[] args )
{
AsyncTest test = new AsyncTest( );
Thread thread1 = new Thread(new ThreadStart(test.SyncLock1));
Thread thread2 = new Thread( new ThreadStart( test.UnsyncLock1 ) );

thread1.Start( );
thread2.Start( );
Console.ReadLine( );
}
}

public class AsyncTest
{
private object lock1 = new object( );

public void SyncLock1( )
{
lock ( lock1 )
{
for ( int i = 1; i <= 20; i++ )
{
Console.Write( "SyncLock1 {0}|", i );
Thread.Sleep( 20 );
}
}
}

public void UnsyncLock1( )
{
for ( int i = 1; i <= 20; i++ )
{
Console.Write( "UnsyncLock1 {0}|", i );
Thread.Sleep( 20 );
}
}
}

Auf der Konsole wird folgendes ausgegeben:


Man kann also sehen, dass die beiden Threads sich nicht von dem lock des einen beeindrucken lassen. Warum auch, schließlich wird keiner durch das lock tangiert.

Ändern wir das Programm etwas ab und fügen eine Methode hinzu die ebenfalls durch ein lock verriegelt ist:

class Program
{
    static void Main( string[] args )
    {
        AsyncTest test = new AsyncTest( );
        Thread thread1 = new Thread(new ThreadStart(test.SyncLock1));
        Thread thread2 = new Thread( new ThreadStart(test.SyncLock2 ) );

        thread1.Start( );
        thread2.Start( );
        Console.ReadLine( );
    }
}

public class AsyncTest
{
    private object lock1 = new object( );
    private object lock2 = new object( );

    public void SyncLock1( )
    {
        lock ( lock1 )
        {
            for ( int i = 1; i <= 20; i++ )
            {
                Console.Write( "SyncLock1 {0}|", i );
                Thread.Sleep( 20 );
            }
        }
    }

    public void SyncLock2( )
    {
        lock ( lock2 )
        {
            for ( int i = 1; i <= 20; i++ )
            {
                Console.Write( "SyncLock2 {0}|", i );
                Thread.Sleep( 20 );
            }
        }
    }
}

Die Konsolenausgabe gleicht der des ersten Beispiels:


Denn auch hier ist es so, dass beide Methoden unabhängige Locks verwenden, sodass keine die andere beeinflusst.

Verriegeln wir nun die zwei Methoden mit demselben lock-Objekt:

class Program
{
    static void Main( string[] args )
    {
        AsyncTest test = new AsyncTest( );
        Thread thread1 = new Thread(new ThreadStart(test.SyncLock1));
        Thread thread2 = new Thread( new ThreadStart( test.SyncLock3 ) );

        thread1.Start( );
        thread2.Start( );
        Console.ReadLine( );
    }
}

public class AsyncTest
{
    private object lock1 = new object( );
    
    public void SyncLock1( )
    {
        lock ( lock1 )
        {
            for ( int i = 1; i <= 20; i++ )
            {
                Console.Write( "SyncLock1 {0}|", i );
                Thread.Sleep( 20 );
            }
        }
    }

    public void SyncLock3( )
    {
        lock ( lock1 )
        {
            for ( int i = 1; i <= 20; i++ )
            {
                Console.Write( "SyncLock3 {0}|", i );
                Thread.Sleep( 20 );
            }
        }
    }
}

Die Methoden werden nun nacheinander abgearbeitet:



Was bedeutet dies nun?

Auf ein Objekt kann nur ein lock (intern ja eigentlich ein Monitor) vergeben werden. Verwenden verschiedene Methoden verschiedene lock-Objekte, beeinflussen sich diese Methoden nicht. Verwenden sie das gleiche lock-Objekt, warten alle Threads, die die Critical Section betreten wollen, bis sie Einer nach dem Anderen das lock auf das Objekt erhalten. Damit einher geht natürlich die Notwendigkeit, dass ein Thread, der eine Critical Section betritt, beim Verlassen dieser das Lock-Objekt wieder freigibt, sodass ein anderer Thread die Critical Section betreten kann.

Links

Ausblick

Im nächsten Teil werde ich das Synchronisationsobjekt Monitor beleuchten. Bis dahin freue ich mich über Rückmeldungen zu diesem Teil.

Sunday, June 08, 2008 4:54:14 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [0] | Trackback
# Friday, May 23, 2008

UnitTesting mit Visual Studio 2008

Robert Mühsig hat auf dem Code-Inside Blog einen wunderbaren Post über den Einstieg in Unit Testing mit Visual Studio 2008 geschrieben. Ich persönlich bin bisher ebenfalls von den von Robert angeführten Gründen noch kein Fan von Unit Testing, weiß jedoch, dass es die Qualität von Software verbessert (gegenüber keinen Unit Tests). Angetriggert durch seinen Post werde ich mich nun auch einmal mit dem der Thematik auseinander setzen. Der Einstieg scheint nicht so schwer zu sein (dank der mittlerweile hervorragenden Integration in Visual Studio) und verlieren kann man auch nichts.

Friday, May 23, 2008 9:41:56 AM (W. Europe Daylight Time, UTC+02:00) #  Comments [0] | Trackback
# Thursday, May 15, 2008

Erster FoeBuD-Treff in Siegen

Der Bielefelder Verein FoeBuD (Verein zur Förderung des öffentlichen bewegten und unbewegten Datenverkehrs e.V.) hat nach jahrelanger erfolgreicher Arbeit sein Einzugsgebiet für aktive Mitstreiter vergrößert. War bisher Bielefeld der Ort, an dem sich die Mitglieder trafen, so gibt es nun ein Konzept für FoeBud-Treffs (man könnte es mit den Chaos-Treffs des CCC vergleichen, wenn es auch nicht das gleiche ist). Der erste FoeBud-Treff wurde nun in Siegen gegründet, und ich bin dabei. Momentan noch nicht als Mitglied im FoeBuD, aber das wird sich in näherer Zukunft ändern.

Wer Interesse an der Mitarbeit hat oder sich einfach über Themen wie Datenschutz, Videoüberwachung oder ähnliches informieren möchte, ist herzlich eingeladen, uns bei einem Treffen zu besuchen oder uns auf anderem Wege zu kontaktieren. Regelmäßiger Treffpunkt ist das 7inch in der Siegener Oberstadt. Die Treffen finden jeweils am zweiten und vierten Mittwoch des Monats statt.

Thursday, May 15, 2008 8:42:44 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [0] | Trackback
# Thursday, May 08, 2008

Neues Spielzeug gefällig?

Ich hoffe, dass mein Handyvertrag jetzt schneller ausläuft und mein Provider des Vertrauens das kleine Juwel in sein Portfolio übernimmt. Man sehe uns staune... das iPhone ist ja wohl ein Witz dagegen. Darauf läuft ja noch nicht einmal ein .NET Framework ;)

Thursday, May 08, 2008 11:35:36 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [2] | Trackback
# Wednesday, May 07, 2008

Powershell TCP Listener

The project I'm currently working on is an embedded device without any graphical interface capability. The only ways to get some information out is the NIC or a serial port. There is also a CAN-Bus interface, but as far as my company developed the device it is better to not base our debugging capabilities on a potentially buggy part of the system. Out first debug out was implemented as a serial output tracer. As the day comes closer that out hardware prototypes will arrive, the higher the need to port the tracing over to the NIC as far as the final hardware won't have a serial port on it.
So last week the network tracer was check in into source control. Now, how to read these information?
Windows ships with Hyper Terminal. But this isn't very comfortable and you manually have to reestablish a lost connection. In my today's lunch break I wrote a small powershell script that listens to the network socket. At the moment I'm porting it to C# to add some more features.

But here is the first part, a small and simple powershell script to listen to a network socket. But please let me clarify that this is code snippet is NOT the way code. It was a fast hack to get it running. There is no error handling and ressources are not freed gracefully as it has to be terminated with ctrl + c. In clear words: This is a sample on how to receive data from a network socket.

$socket = new-object System.Net.Sockets.TcpClient("172.16.170.123", 9950)
if($socket -eq $null) { return; }
$stream = $socket.GetStream()
$buffer = new-object System.Byte[] $socket.ReceiveBufferSize
$encoding = new-object System.Text.AsciiEncoding

while($true)
{
   if($stream.DataAvailable)
   {
      $read = $stream.Read($buffer, 0, $socket.ReceiveBufferSize)  
      write-host -n ($encoding.GetString($buffer, 0, $read))
   }
}

Wednesday, May 07, 2008 9:47:07 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [0] | Trackback
# Sunday, May 04, 2008

Reaktionen auf den Flashmob

Ich hätte nie gedacht, dass der gestrige Flashmob-Versuch solch eine Aufruhr auf meinem kleinen, bescheidenen Blog erzeugt. In den Kommentaren zu meinem gestrigen Eintrag meldet sich ein Kritiker zu Wort, per Mailformular bekomme ich anonyme Hinweise, wer sich für die Aktion verantwortlich fühlt etc...

Disclaimer
Eines kurz zur Klarstellung: Diese Blog ist kein Flashmob-Blog. Ich habe lediglich aus lokalpatriotischem Antrieb etwas über den Flashmob geschrieben. Dieser Blog dient nicht zum Meinungsaustausch, zum bashen und flamen von Teilnehmern, Organisatoren etc. Mir unaufgefordert zugesendete Informationen ohne Absender werde ich nicht veröffentlichen. Ich fühle mich weiter nicht dazu verpflichtet, meine persönliche Meinungsäußerung vom gestrigen Tag zu widerrufen oder klarzustellen.

Sunday, May 04, 2008 2:09:52 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [1] | Trackback
# Saturday, May 03, 2008

Flashmob versus Shutdown Day

Wie man sieht, habe ich es nicht geschafft, der Shutdown Day-Bewegung zu folgen. Für mich war dieser bereits gestern. Heute gab es jedoch ein Ereignis, von dem berichtet werden muss:
In Siegen gab es einen Flashmob!
Laut der SchülerCC-Gruppe, die wohl die Hauptorganisation der Aktion durchführte, war dies der zweite Flashmob in Siegen. Ich war mit der Kamera vor Ort, konnte jedoch keine brauchbaren Ergebnisse erzielen.

Was ist ein Flashmob?
Laut Wikipedia ist ein Flashmob ein Blitzauflauf. Per Internet wird ein Treffpunkt organisiert, an dem sich dann scheinbar aus dem Nichts ein immenser Auflauf bildet. Der gesamte Mob führt dabei die gleiche Handlung aus. Nach der Aktion löst sich der Mob ebenso schnell in alle Richtungen auf, wie er sich gebildet hat.

Was in Siegen passierte?
Gegen 14:45 Uhr füllt sich langsam, aber sicher, das Erdgeschoss der dort ansässigen Einkaufsmeile. Die Sicherheitskräfte waren augenscheinlich eingeweiht, da teilweise Rolltreppen von anderen Stockwerken ins Erdgeschoss abgestellt wurden.  Gegen 14:55 Uhr war allerdings fast kein Durchkommen mehr. Von Passanten wurden wir gefragt, was denn hier los sei, worauf ein Bekannter sagte, es werde eine Autogrammstunde mit einem noch nicht bekannten DSDS-Teilnehmer geben. Gegen 15:00 Uhr konnte ich den angekündigten Pfiff vernehmen, worauf sich im vorderen Teil der Einkaufspassage alle auf den Boden legten. In dem Bereich, in dem ich mehr oder weniger eingeklemmt war, wurde der Pfiff gnadenlos ignoriert. Dadurch breitete sich das Ganze mehr als Laola durch die Passage, was jedoch den kollektiven Eindruck extrem störte. Nach noch nicht einmal einer Minute standen jedoch schon wieder alle, von den angekündigten zwei Minuten war also nur noch die Hälfte übrig geblieben. Somit war es für mich extrem schwer, aus der dunklen Ecke, in der ich eingezwängt war, in die helleren Bereiche vorzudringen. Als ich dort war, war das Spektakel bereits vorbei und es waren keine guten Bilder in der Kiste. Leider.

Persönliche Eindrücke
  1. Gut, dass es eine Bewegung gibt, die absolut unpolitisch die verschiedensten Jugendgruppen anspricht.
  2. Gut, dass die Jugendlichen mit solchen Aktionen feststellen können, dass sie es locker schaffen, den normalen Verkehr in einer Einkaufspassage komplett zum erliegen zu bringen.
  3. Gut, dass es sehr viele Teilnehmer waren.

  4. Schlecht, dass fast mehr Schaulustige als Teilnehmer anwesend waren.Schlecht, dass sich eine solche Menge (ich schätze ca. 300 Teilnehmer) nur auf zwei Haufen ballt. In vielen kleinen Gruppen auf allen drei Stockwerken wäre die Aktion viel imposanter gewesen.
  5. Schlecht, dass eine von zwei Gruppen den Einsatz total verpennt.
  6. Schlecht, dass es nur einen Organisator gab, der es nicht geschafft hat, die Gruppen zu verteilen, wobei er vorher bei beiden Gruppen mit Leuten gesprochen hat. Dort hätte es mehrere geben müssen, die sich auf die Aktion vorbereitet haben müssten.
  7. Schlecht, dass es nicht sehr spontan aussah. Wenn man wusste, was passiert, konnte man bereits ab 14:35 Uhr die ersten Anzeichen erkennen.
Fazit
Bleibt zu hoffen, dass solche Aktionen in Zukunft wiederholt werden (natürlich nicht die selben) und dass sich die Organisatoren etwas mehr zutrauen. Lieber 50 Leute auf drei Stockwerken verteilen, als 300 auf einem Haufen. Und mir persönlich wäre es sehr lieb, wenn man die angekündigten Zeiten erstens einhalten und zweitens verlängern könnte. Denn nichts beschreibt einen Flashmob besser als Bilder. Bei solch kurzen Aktionen ist es jedoch extrem schwierig, vernünftige Resultate zu erzielen.

Saturday, May 03, 2008 4:21:35 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [8] | Trackback
# Saturday, April 26, 2008

Shutdown Day 2008



Durch Damir Tomicic darauf aufmerksam geworden; und nun bin ich auch dabei.

Saturday, April 26, 2008 12:35:59 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [0] | Trackback
# Sunday, April 20, 2008

Häuser wie Software

Und wieder hat der Code-Inside Blog einen sehr interessanten, diesmal weniger lustigen, weil leider wahren Artikel in seinem Blog veröffentlich. Allerdings hätte ich Mark durch Euro ersetzt; Was aber zeigt, dass das Problem bereits sehr lange bekannt und doch immer noch nicht gelöst wurde.

Sunday, April 20, 2008 12:37:51 PM (W. Europe Daylight Time, UTC+02:00) #  Comments [0] | Trackback
# Friday, April 18, 2008

Excel: Zellen verbinden per Shortcut

Ich musste gerade einige Zellen in Excel verbinden. Da mir das ewige Markieren - Rechtsklick - Zellen Formatieren - Zellen verbinden zu langsam war, habe ich nach einem Shortcut für diese Aktion gesucht. Leider bringt Excel so etwas nicht mit. Aber mit einem kleinen Macro kann man die Funktionalität nachrüsten:

Diese Makro wechselt nach der Zuweisung einer Tastenkombination den Status von mehreren Zellen von Nicht verbunden zu Verbunden und wieder zurück.
Sub Makro2()
    With Selection
        .HorizontalAlignment = xlGeneral
        .VerticalAlignment = xlBottom
        .WrapText = False
        .Orientation = 0
        .AddIndent = False
        .IndentLevel = 0
        .ShrinkToFit = False
        .ReadingOrder = xlContext
        .MergeCells = Not .MergeCells
    End With
End Sub

Friday, April 18, 2008 9:56:58 AM (W. Europe Daylight Time, UTC+02:00) #  Comments [0] | Trackback