From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Thread: 103376,3a3dffa82925efee X-Google-Attributes: gid103376,public X-Google-Language: ENGLISH,ASCII-7-bit Path: g2news1.google.com!news2.google.com!fu-berlin.de!uni-berlin.de!not-for-mail From: "Nick Roberts" Newsgroups: comp.lang.ada Subject: Re: Advantages Date: Sun, 27 Jun 2004 16:16:59 +0100 Message-ID: <2k86nbF18idtrU1@uni-berlin.de> References: X-Trace: news.uni-berlin.de woLNqrZaP5fzTVMiqcUaxgXJSsc22gx+UZiFe2AZlajzSWXLY= X-Priority: 3 X-MSMail-Priority: Normal X-Newsreader: Microsoft Outlook Express 6.00.2800.1409 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1409 Xref: g2news1.google.com comp.lang.ada:1951 Date: 2004-06-27T16:16:59+01:00 List-Id: "Andrew Carroll" wrote in message news:mailman.165.1088318818.391.comp.lang.ada@ada-france.org... > ... What features of Ada make it easier to detect pitfalls > in parallel programming? What features of Ada help with > debugging? I don't wish to cross with Marin in answering this, but forgive me if I throw my oar in a bit. Suppose I have two threads (another name for 'tasks') which both need to read a variable (which is a struct (record)) from time to time, and one of them also updates the variable from time to time. When an update is done, it involves assigning values to several of the variable's members (components), so the update is not 'atomic'. This means that there needs to be some kind of synchronisation between the threads, to prevent one thread trying to read the variable right in the middle of it being updated. In most programming languages, including C for example, under a typical execution environment or OS, the usual or only way to do this will be by the explicit use of semaphores (or a similar facility). When a thread wants to read or update the variable, it sets the semaphore (which may cause it to wait (to be 'blocked')). When it has finished, it resets the semaphore (which may unblock another waiting thread). Let's say the function to set a semaphore is named 'sem_p', and the function to reset it is 'sem_v'. If the variable is named 'r' (and has members 'x' and 'y') and the semaphore is named 's', a C program might contain code like this: /* Thread A */ ... sem_p(s); /* set the semaphore, maybe wait */ x = r.x; y = r.y; /* read the variable */ sem_v(s); /* reset the semaphore */ ... /* Thread B */ ... sem_p(s); /* set the semaphore, maybe wait */ r.x = x; r.y = y; /* update the variable */ sem_v(s); /* reset the semaphore */ ... This is all well and dandy, but there are many things that can go wrong. For example, supposing a programmer accidentally omitted one of the calls to sem_p or sem_v? Or accidentally used the wrong semaphore in one of the calls? This kind of mistake is easily done in a big, complex program with lots of threads and semaphores. The consequences are often hard to debug: it is typical that a problem is only manifested intermittently; it is typical that using a debugger (single stepping or breakpoint jumping) destroys the temporal circumstances of normal execution that are needed to observe the problem at all; it is typical that, even when the problem is observed, the cause is subtle and very hard to determine. In Ada 95, you would typically use a protected object to protect the variable from unwanted parallel access. (In Ada 83 you would typically have used a task and a 'rendezvous', but I'll illustrate the protected object approach here.) type R_Type is record X, Y: Float; ... end record; protected Prot_R is function Get return R_Type; procedure Set (New_R: in R_Type); private Inner_R: R_Type; end; protected body Prot_R is function Get return R_Type is begin return Inner_R; end; procedure Set (New_R: in R_Type) is begin Inner_R := New_R; end; end Prot_R; ... --- in task A declare Local_R: R_Type; begin Local_R := Prot_R.Get; -- read variable X := Local_R.X; Y := Local_R.Y; ... --- in task B Prot_R.Set((X,Y,...)); -- update variable Because the use of OS primitives such as semaphores is hidden from the user by Ada, many of the mistakes that would be easy to make in the C code are either impossible -- for example, we cannot omit a call to set or reset a semaphore, since Ada does these for us invisibly -- or caught by the compiler and reported to us in an obvious way -- for example, if we tried to use 'Inner_R' in an expression in task A or B, the compiler would complain that Inner_R is not visible at that point (because it is private to Prot_R). Obviously this example is simplistic compared to real software, but I hope it gives a flavour of Ada's advantages in concurrent programming. You might like to note that the use of an Ada protected object gives further advantages than I have mentioned so far. It is implemented in such a way that functions are permitted to be parallel with one another (but not with any procedure). So, tasks A and B would be permitted to read Prot_R simultaneously, but no task would be allowed to read it while another was in the middle of setting it. Protected objects can provide yet more sophisticated functionality (with 'entries'). And then there are tasks, rendezvouses, and various other facilities too. All of this functionality can be achieved in other languages, but generally only at a lower and more error-prone level. Really, one could write a big book about concurrent programming in Ada. Hehe, in fact I'm sure somebody has! I'm sorry, I can't recall the details, but I'm sure someone else will oblige. -- Nick Roberts