2. cvičení


Vlákna v programovacích jazycích

LISP

Pro potřeby tohoto předmětu budeme používat knihovnu od Dr. Osičky, která je ke stažení zde. V souboru load.lisp je nutné změnit cesty. Pak stačí pomocí LispWorks načíst soubor pomocí (Compile and Load...) load.lisp.

Instalace QuickLisp

  1. Quicklisp lze stáhnout na stránce https://www.quicklisp.org/beta/.
  2. Stažený soubor quicklisp.lisp načíst pomoci File > Compile and Load.
  3. Spustit instalaci pomocí (quicklisp-quickstart:install).
  4. Spustit příkaz (ql:system-apropos "vecto").
  5. Spustit příkaz (ql:quickload "vecto").
  6. Jako posledni (ql:add-to-init-file).

Instalace QuickLisp vytvoří složku quicklisp v domovském adresáři (Linux, macOS) nebo ve složce Users\jmeno (Windows).

Použití

    
        ;; aprogn (asynchronni progn)
        ;; syntax: (a-progn
        ;;             forma1
        ;;             forma2
        ;;             ...)
        ;; semantika: asynchronne vola vyhodnoceni forem, kazdou v jednom vlaknu
        ;;            vraci seznam zavolanych vlaken, ve vlaknech je navazana
        ;;            promenna pro standardni vystup

        (a-progn
          (+ 1 2)
          (* 2 2)
          (/ 2 2))

        ;; asynchroni dolist
        ;; syntax:  (a-dolist (var list)
        ;;            body)
        ;; semantika: postupne vaze prvky list na promenou var, pro kazde navazani
        ;;            spusti body ve vlastnim vlakne, ve vlaknech je navazana promenna
        ;;            pro standardni vystup
        ;; navratova hodnota: seznam spustenych vlaken, vlakna jsou pojmenovana podle hodnot var

        (a-dolist (i '(1 2 3 4))
          (print i))
    

Java

Třída Thread

V Javě máme dvě možnosti jak pracovat s vlákny

  1. Pokud chceme kontrolovat proces vztvoření a řízení vlákna, pak stačí vytvořit instanci třídy Thread pokaždé když bude aplikace potřebovat nové vlákno.
  2. Pro abstraktní práci s vlákny je možné použít tzv. Executors, více zde.
    
    //Vytvoření vlákna pomocí dědění třídy Thread
    public class HelloThread extends Thread {

        public void run() {
            System.out.println("Hello from a thread!");
        }

        public static void main(String args[]) {
            (new HelloThread()).start();
        }
    }

    //Vytvoření nového vlákna implementací rozhraní Runnable
    public class HelloRunnable implements Runnable {

        public void run() {
            System.out.println("Hello from a thread!");
        }

        public static void main(String args[]) {
            (new Thread(new HelloRunnable())).start();
        }
    }
    

Pozastavení vykonávání

Pokud chceme pozastavit výkonávání programu v určitém vlákně, tak k tomu použijeme metodu Thread.sleep. Pomocí této metody zastavíme vykonávání programu na určitou dobu. Čekání není aktivní takže nezatěžuje procesor a ten může být přídelen jinému vláknu.

    
    public class SleepMessages {

        public static void main(String args[]) throws InterruptedException {
            String importantInfo[] = {
                "Mares eat oats",
                "Does eat oats",
                "Little lambs eat ivy",
                "A kid will eat ivy too"
            };

            for (int i = 0; i < importantInfo.length; i++) {
                //Pozastavení programu na 4 sekundy
                Thread.sleep(4000);

                System.out.println(importantInfo[i]);
            }
        }
    }
    

Přerušení vlákna

Každé vlákno může být přerušeno pomocí metody interrupt(). Pokud je vlákno přerušeno tak by mělo ukončít svojí činnost.

    
    public static void main(String[] args) throws IOException, InterruptedException {

        Thread printer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(2000);
                    System.out.println(i + ". Hello World!");
                } catch (InterruptedException e) {
                    System.err.println(e.getMessage());
                    return;
                }

            }
        });

        printer.start();

        Thread.sleep(3000);
        printer.interrupt();
    }

    //Výstup
    // 0. Hello World!
    // sleep interrupted

    

Join

Vlákno může čekat na dokončení jiného vlákna. Čekání se provádí metodou join().

    
    public static void main(String[] args) throws IOException, InterruptedException {

        Thread printer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(2000);
                    System.out.println(i + ". Hello World!");
                } catch (InterruptedException e) {
                    System.err.println(e.getMessage());
                    return;
                }
            }
        });

        printer.start();
        // Hlavní vlákno bude čekat na dokončení vlákna printer
        printer.join();

        System.out.println("Printer Thread complete.");
    }

    

Jazyk C

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>

    void *print_message_function( void *ptr );

    main()
    {
         pthread_t thread1, thread2;
         char *message1 = "Thread 1";
         char *message2 = "Thread 2";
         int  iret1, iret2;

        /* Create independent threads each of which will execute function */

         iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
         iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);

         /* Wait till threads are complete before main continues. Unless we  */
         /* wait we run the risk of executing an exit which will terminate   */
         /* the process and all threads before the threads have completed.   */

         pthread_join( thread1, NULL);
         pthread_join( thread2, NULL);

         printf("Thread 1 returns: %d\n",iret1);
         printf("Thread 2 returns: %d\n",iret2);
         exit(0);
    }

    void *print_message_function( void *ptr )
    {
         char *message;
         message = (char *) ptr;
         printf("%s \n", message);
    }


    // VYSVETLENI FUNKCI

    int pthread_create(	pthread_t * thread,
            const pthread_attr_t * attr,
                    void * (*start_routine)(void *),
                    void *arg);

    /*
    Arguments:
    thread - returns the thread id. (unsigned long int defined in bits/pthreadtypes.h)

    attr - Set to NULL if default thread attributes are used. (else define
    members of the struct pthread_attr_t defined in bits/pthreadtypes.h)
    Attributes include:

      - detached state (joinable? Default: PTHREAD_CREATE_JOINABLE. Other
        option: PTHREAD_CREATE_DETACHED)
      - scheduling policy (real-time? PTHREAD_INHERIT_SCHED,PTHREAD_EXPLICIT_SCHED,SCHED_OTHER)
        scheduling parameter
      - inheritsched attribute (Default: PTHREAD_EXPLICIT_SCHED Inherit from
        parent thread: PTHREAD_INHERIT_SCHED)
      - scope (Kernel threads: PTHREAD_SCOPE_SYSTEM User threads:
        PTHREAD_SCOPE_PROCESS Pick one or the other not both.)
      - guard size
      - stack address (See unistd.h and bits/posix_opt.h _POSIX_THREAD_ATTR_STACKADDR)
      - stack size (default minimum PTHREAD_STACK_SIZE set in pthread.h),

    void * (*start_routine) - pointer to the function to be
    threaded. Function has a single argument: pointer to void.

    *arg - pointer to argument of function. To pass multiple arguments,
     send a pointer to a structure.

    ---------------
    */

    void pthread_exit(void *retval);

    /*
    Arguments:
    retval - Return value of thread.

    This routine kills the thread. The pthread_exit function never
    returns. If the thread is not detached, the thread id and return value
    may be examined from another thread by using pthread_join.  Note: the
    return pointer *retval, must not be of local scope otherwise it would
    cease to exist once the thread terminates

    ---------------
    */

    int pthread_join(pthread_t thread, void **value_ptr);

    /*
    The pthread_join() function shall suspend execution of the calling
    thread until the target thread terminates, unless the target thread
    has already terminated. On return from a successful pthread_join()
    call with a non-NULL value_ptr argument, the value passed to
    pthread_exit() by the terminating thread shall be made available in
    the location referenced by value_ptr. When a pthread_join() returns
    successfully, the target thread has been terminated. The results of
    multiple simultaneous calls to pthread_join() specifying the same
    target thread are undefined. If the thread calling pthread_join() is
    canceled, then the target thread shall not be detached.

    It is unspecified whether a thread that has exited but remains
    unjoined counts against {PTHREAD_THREADS_MAX}.

    The behavior is undefined if the value specified by the thread
    argument to pthread_join() does not refer to a joinable thread.

    The behavior is undefined if the value specified by the thread
    argument to pthread_join() refers to the calling thread.

    If successful, the pthread_join() function shall return zero;
    otherwise, an error number shall be returned to indicate the error.
    */
    

C#

Níže je uveden příklad spustění vlákna v jazyce C#. Více informací je možné získat v dokumentaci.

    
        using System;
        using System.Threading;

        namespace MultithreadingApplication {
           class ThreadCreationProgram {
              public static void CallToChildThread() {
                 Console.WriteLine("Child thread starts");

                 // the thread is paused for 5000 milliseconds
                 int sleepfor = 5000;

                 Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
                 Thread.Sleep(sleepfor);
                 Console.WriteLine("Child thread resumes");
              }

              static void Main(string[] args) {
                 ThreadStart childref = new ThreadStart(CallToChildThread);
                 Console.WriteLine("In Main: Creating the Child thread");

                 Thread childThread = new Thread(childref);
                 childThread.Start();
                 Console.ReadKey();
              }
           }
        }
    

Úkoly

  1. Napište program, kde budou alespoň 2 vlákna n-krát inkrementovat čítač. Za n zvolte dostatečně velké číslo.

  2. Napište program, kde budou 2 vlákna každé z nich se uspí na náhodnou dobu a pak vypíše text na obrazovku.

  3. Naprogramujte nasledující algoritmus. Před implementací zkuste přijít na to co daný algoritmus dělá.

    Algorithm A
  4. Naprogramujte Welfare crook problem:

    Welfare crook problem
  5. Naprogramujte paralelní násobení matic.


Zdroje

[1] M. BEN-ARI, Principles of Concurrent And Distributed Programming, second edidion, 2006