1/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vláknové programování část XII Lukáš Hejmánek, Petr Holub {xhejtman,hopet}@ics.muni.cz Laboratoř pokročilých síťových technologií PV192 2010–05–20 2/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Přehled přednášky Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní 3/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a GUI GUI jsou obecně řízená událostmi ◾ asynchronní vznik události ◾ obvykle se jedno vlákno stará o obsluhu událostí – “event loop” Problémy událostmi řízeného modelu GUI ◾ problém předávání změn z jiných vláken ◾ problém s dlouho běžícími úlohami obsluhujícími událost 4/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a GUI Možnosti synchronizace mezi vlákny a GUI 1. thread-safe/multi-thread GUI nepoužívá se Graham Hamilton: Multithreaded toolkits: A failed dream? http://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html 2. explicitní zamykání např. Gtk s použitím Gdk zámků 3. předávání práce GUI vláknu Java SWING, QT, GtkAda Contributions 5/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Explicitní zamykání s GUI 6/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Explicitní zamykání s GUI Strategie 1. před startem GUI inicializuji zamykání 2. před startem smyčky obsluhy událostí získám zámek 3. smyčka obsluhy událostí zámek periodicky pouští 4. jiné vlákno, pokud chce kreslit, musí získat zámek a po dokončení jej pustit Problémy ◾ ověření, že při všech aktualizacích GUI získávám zámek ◾ ověření, že po všech aktualizacích GUI pouštím zámek ◾ ověření, že u callbacků nezískávám a nepouštím zámky 7/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Ada binding pro Gtk+, Glib a Gdk ◾ prakticky kompletní wrapper z pohledu Gtk+ ◾ může spolupracovat s tasky ◾ 2.14 stabilní, 2.18 v CVS Dostupné pro Windows, Linux, MacOS X http://libre.adacore.com/libre/tools/gtkada/ 8/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Inicializace Vytvoření smyčky událostí ◾ držení zámku v době startu 1 Gtk.Main.Set_Locale; Gdk.Threads.G_Init; 3 Gdk.Threads.Init; Gtk.Main.Init; 5 Init_GUI; Gdk.Threads.Enter; 7 Gtk.Main.Main; Gdk.Threads.Leave; 9 return; 9/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Modifikace z jiného vlákna Zámek pro modifikace task body Counter is 2 begin Main_Loop : 4 loop Counter_Monitor.Wait_For_Start; 6 -- because the main thread is waiting for us on termination -- we can touch Gtk objects until we signal termination; beware of ogr 8 -- more appropriate is exiting early exit Main_Loop when Counter_Monitor.Check_If_Quit; 10 Gdk.Threads.Enter; -- Ada95 syntax 12 Gtk.Button.Set_Label (Global_Window.all.Run_Button, -" Stop "); Gdk.Threads.Leave; 14 Counter_Loop : for I in 0 .. 1000 loop 16 exit Main_Loop when Counter_Monitor.Check_If_Quit; if Counter_Monitor.Check_If_Stop then 18 exit Counter_Loop; end if; 20 Gdk.Threads.Enter; Gtk.Label.Set_Label (Global_Window.all.Counter_Label, -(Integer’Imag 22 Gdk.Threads.Leave; delay 1.0; 24 end loop Counter_Loop; 10/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Modifikace z jiného vlákna Zámek pro modifikace Gdk.Threads.Enter; 2 -- Ada2005 extended syntax Global_Window.all.Run_Button.all.Set_Label (-" Run "); 4 Counter_Monitor.Finished; Global_Window.all.Run_Button.all.Set_Sensitive (True); 6 Gdk.Threads.Leave; end loop Main_Loop; 8 Counter_Monitor.Finished; end Counter; 11/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Callbacky procedure Quit is 2 begin Counter_Monitor.Quit; 4 -- BEWARE OF OGRES! May deadlock if lock is not given up! Gdk.Threads.Leave; 6 Counter_Monitor.Wait_Until_Finished; Gdk.Threads.Enter; 8 Destroy (Global_Window); Gtk.Main.Main_Quit; 10 end Quit; 12 procedure On_Quit_Button_Clicked (Button : access Gtk.Button.Gtk_Button_Record’Class) 14 is begin 16 pragma Unreferenced (Button); Quit; 18 end On_Quit_Button_Clicked; 12/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Callbacky 1 procedure On_Run_Button_Clicked (Button : access Gtk.Button.Gtk_Button_Record’Class) 3 is begin 5 if Counter_Monitor.Check_If_Running then Button.all.Set_Sensitive (False); 7 Counter_Monitor.Stop; else 9 Counter_Monitor.Start; end if; 11 end On_Run_Button_Clicked; 13/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Callbacky 1 Button_Callback.Connect (Global_Window.all.Quit_Button, 3 "clicked", Button_Callback.To_Marshaller (On_Quit_Button_Clicked’Access), 5 False); -- XXX: window delete event should be also handled, but for simplicity 7 -- reasons, it is ommitted. 9 Button_Callback.Connect (Global_Window.all.Run_Button, 11 "clicked", Button_Callback.To_Marshaller (On_Run_Button_Clicked’Access), 13 False); 14/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Synchronizace mezi GUI vláknem a počítacím vláknem protected Counter_Monitor is 2 procedure Start; entry Wait_For_Start; 4 procedure Stop; function Check_If_Stop return Boolean; 6 procedure Quit; function Check_If_Quit return Boolean; 8 procedure Finished; function Check_If_Running return Boolean; 10 entry Wait_Until_Finished; private 12 Should_Start : Boolean := False; Should_Stop : Boolean := False; 14 Should_Quit : Boolean := False; Is_Running : Boolean := False; 16 end Counter_Monitor; 15/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda Synchronizace mezi GUI vláknem a počítacím vláknem 1 protected body Counter_Monitor is procedure Start is 3 begin Should_Start := True; 5 end Start; 7 entry Wait_For_Start when Should_Start or Should_Quit is 9 begin Should_Start := False; 11 Should_Stop := False; -- don’t touch Should_Quit, so that it’s persistant 13 Is_Running := True; end Wait_For_Start; 16/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Gtk http://www.gnu.org/software/guile-gnome/docs/gdk/ html/Threads.html 17/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Asynchronní předávání do GUI 18/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Asynchronní předávání do GUI Strategie 1. vytvoříme smyčku obsluhy událostí GUI, která volá přímo jednotlivé callbacky 2. vlákno, které chce aktualizovat GUI předá práci GUI smyčce 3. do vlákna zpracovávajícího události můžeme zasílat blokujícím (Request) nebo neblokujícím (Send) způsobem Problémy ◾ ověření, že při všech aktualizacích GUI používáme předání práce GUI vláknu 19/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda a Gtk.Main.Router GtkAda Contributions: http://www.dmitry-kazakov.de/ada/gtkada_ contributions.htm Inicializace Vytvoření smyčky událostí ◾ není třeba podpora vláken z Gdk Gtk.Main.Set_Locale; 2 Gtk.Main.Init; Gtk.Main.Router.Init; 4 Init_GUI; Gtk.Main.Main; 6 return; 20/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda a Gtk.Main.Router Zasílání z vlákna o smyčky událostí 1 task body Counter is begin 3 Main_Loop : loop 5 Counter_Monitor.Wait_For_Start; -- if we don’t exit here when appropriate, the application would 7 -- deadlock: the GUI task callback is waiting for us while we’re -- synchronously calling modification of the GUI 9 exit Main_Loop when Counter_Monitor.Check_If_Quit; Gtk.Main.Router.Request (+Update_GUI_Button_Start’Access); 11 Counter_Loop : for I in 0 .. 1000 loop 13 exit Main_Loop when Counter_Monitor.Check_If_Quit; if Counter_Monitor.Check_If_Stop then 15 exit Counter_Loop; end if; 21/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda a Gtk.Main.Router Zasílání z vlákna o smyčky událostí declare 2 Label : aliased Unbounded_String := To_Unbounded_String (Integer’ begin 4 Update_GUI_Label_Callback.Request (Update_GUI_Label’Access, Label end; 6 delay 1.0; end loop Counter_Loop; 8 declare NR : aliased Null_Record; 10 begin -- this goes asynchronously 12 Update_GUI_Button_End_Handler.Send (Update_GUI_Button_End’Access, NR end; 14 Counter_Monitor.Finished; end loop Main_Loop; 16 Counter_Monitor.Finished; end Counter; 22/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda a Gtk.Main.Router Registrace volání ◾ generika ◾ komplikované kvůli typům 1 procedure Update_GUI_Button_Start is begin 3 Gtk.Button.Set_Label (Global_Window.all.Run_Button, -" Stop "); end Update_GUI_Button_Start; 5 type Null_Record is null record; 7 procedure Update_GUI_Button_End (NR : in out Null_Record) is pragma Unreferenced (NR); 9 begin Global_Window.all.Run_Button.all.Set_Label (-" Run "); 11 Global_Window.all.Run_Button.all.Set_Sensitive (True); end Update_GUI_Button_End; 13 procedure Update_GUI_Label (Label_Access : access Unbounded_String) is 15 begin Gtk.Label.Set_Label (Global_Window.all.Counter_Label, -To_String (Label_Ac 17 end Update_GUI_Label; 23/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní GtkAda a Gtk.Main.Router Registrace volání ◾ generika ◾ komplikované kvůli typům 1 -- this is ugly type Local_Callback is access procedure; 3 function "+" is new Ada.Unchecked_Conversion (Local_Callback, Gtk.Main.Router.Gtk_Callback 5 -- this is better 7 package Update_GUI_Label_Callback is new Gtk.Main.Router.Generic_Callback_Req 9 package Update_GUI_Button_End_Handler is new Gtk.Main.Router.Generic_Message 24/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Java SWING Multiplatformní GUI v Javě SWING single-thread rule ◾ všechny prvky mohou být vytvářeny, měněny a dotazovány pouze z vlákna obsluhujícího události ◾ SwingUtilities.isEventDispatchThread – kontrola, zda jsme ve vlákně obsluhující události ◾ SwingUtilities.invokeLater – předávání Runnable do vlákna obsluhujícího události ◾ SwingUtilities.invokeAndWait – předávání Runnable do vlákna obsluhujícího události a zablokuje se do dokončení akce ◾ callbacky se řeší pomocí akcí action listener z vlákna obsluhujícího události ◾ dlouho běžící callbacky je možno odštípnout do nového vlákna (přímo nebo přes Executory) 25/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Java SWING Inicializace 1 public class JavaGUI { 3 public static void main(String[] args) { CounterDialog dialog = new CounterDialog(); 5 dialog.setSize(400,300); dialog.setVisible(true); 7 } 9 } 26/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Java SWING Předávání vláknu událostí SWINGu @Override 2 public void run() { for (int i = 0; i <= 1000; i++) { 4 if (shouldShutdown.get()) { break; 6 } final String labelText = String.valueOf(i); 8 javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { 10 counterLabel.setText(labelText); } 12 }); try { 14 Thread.sleep(1000); } catch (InterruptedException ignored) { 16 } } 18 runButton.setText("Run"); runButton.setEnabled(true); 20 } 27/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Java SWING Callbacky 1 buttonRun.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { 3 onRun(); } 5 }); private void onRun() { 2 if (!isRunning.get()) { buttonRun.setText("Stop"); 4 isRunning.set(true); if (counterThread != null) { 6 try { counterThread.join(); 8 } catch (InterruptedException ignored) { } 10 } counterThread = new CounterThread(counterLabel, buttonRun); 12 counterThread.start(); } else { 14 buttonRun.setEnabled(false); counterThread.requestShutdown(); 16 counterThread.interrupt(); isRunning.set(false); 18 } } 28/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní QT http://doc.trolltech.com/3.3/threads.html 29/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní OpenGL Průmyslový standard specifikující multiplatformní rozhraní pro tvorbu aplikací počítačové grafiky Existuje v řadě verzí od 1.0 po poslední 4.0 Aplikace využívající OpenGL je schopna ±běžet na různém HW ◾ OpenGL podporuje různá rozšíření, která spolu se změnami verzí zhoršují poratabilitu ◾ Aplikace musí umět detekovat co daná platforma nabízí Rasterizaci objektů provádí obvykle HW, existují ale i SW razterizátory (Mesa) 30/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní OpenGL pipeline OpenGL funkce jsou asynchronní OpenGL je stavová Návrh scény provádíme: glBegin( GL_POLYGON ); /* Begin issuing a polygon */ 2 glColor3f( 0, 1, 0 ); /* Set the current color to green */ glVertex3f( -1, -1, 0 ); /* Issue a vertex */ 4 glVertex3f( -1, 1, 0 ); /* Issue a vertex */ glVertex3f( 1, 1, 0 ); /* Issue a vertex */ 6 glVertex3f( 1, -1, 0 ); /* Issue a vertex */ glEnd(); /* Finish issuing the polygon */ Problematické při použití vláken 31/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní OpenGL kontext Kontex OpenGL není zachycen OpenGL specifikací, je tedy implementačně závislý Kontex uchovává stav a další informace pro rasterizér Existují rozšíření OpenGL pro manipulaci s kontexty ◾ WGL – wglCreateContext, wglMakeCurrent, wglDeleteContext ◾ GLX – glXCreateNewContext, glXMakeCurrent, glXDestroyContext ◾ Pozor na použití glXDestroyContext – OpenGL je asynchronní! Pouze jeden kontext může být aktivní v rámci 1 vlákna Kontext je často uložen v TLS 32/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní OpenGL kontext a vlákna S implicitním kontextem ◾ OpenGL na MacOS dovoluje přistup k GL funkcím z více vláken ◾ Windows ohlásí resource busy ◾ Linux (s Nvidia drivery) končí segmentation fault ◾ Obecně je u vláken s implicitním kontextem problém, že kontext má právě master vlákno ◾ Nelze triviálně předat kontext jinému vláknu (zejména u GLX) Různá vlákna si mohou vytvořit svůj kontext ◾ Nemohou pak ale kreslit jednu společnou scénu přímo (lze přes nepřímý rendering do bufferu) ◾ U WGL to často znamená, že každé vlákno kreslí do jiného okna 33/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní OpenGL kontext a vlákna Změna kontextu na jiné než master vlákno ◾ Při použití libSDL triviálně tak, že zavoláme SDL_init() z ne-master vlákne, o zbytek se postará libSDL ◾ Implicitní kontext vytváří GLX rozšíření Xserveru samo o sobě ◾ Jediná cesta pro GLX je reload GLX z vlákna, které má kreslit ◾ Návod lze najít např. ve zdrojových kódech libSDL src/video/x11/SDL_x11gl.c 34/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Java Native Interfaces Volání nativních metod z Javy Struktura JNI volání 1 /* C */ JNIEXPORT void JNICALL Java_ClassName_MethodName 3 (JNIEnv *env, jobject obj, jstring javaString) { 5 //ziskani nativniho retezce z javaString const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0); 7 ... //nezapomenout uvolnit! 9 (*env)->ReleaseStringUTFChars(env, javaString, nativeString); } 11 // C++ 13 JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj, jstring javaString) 15 { //ziskani nativniho retezce z javaString 17 const char *nativeString = env->GetStringUTFChars(javaString, 0); ... 19 //nezapomenout uvolnit! env->ReleaseStringUTFChars(javaString, nativeString); 21 } 35/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a JNI Ukazatel na JNIEnv je platný pouze z vlákna, jemuž je přiřazen ◾ nelze předávat mezi vlákny ◾ stejný při opakovaných voláních v témže vlákně Lokální reference nesmí opustit vlákno ◾ lokální reference jsou platné pouze v rámci daného volání nelze se je uschovávat ve static proměnných ◾ převést na globální, pokud je třeba (NewGlobalRef) ◾ globální reference vylučují objekt z garbage collection ◾ existují slabé lokální reference (NewWeakGlobalRef) umožňují garbage collection odkazovaného objektu potřeba kvůli class unloading musí se kontrolovat, zda odkazovaný objekt existuje (IsSameObject s NULL parametrem) 36/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a JNI static jclass stringClass = NULL; 2 ... if (stringClass == NULL) { 4 jclass localRefCls = (*env)->FindClass(env, "java/lang/String"); 6 if (localRefCls == NULL) { return NULL; /* exception thrown */ 8 } /* Create a global reference */ 10 stringClass = (*env)->NewGlobalRef(env, localRefCls); 12 /* The local reference is no longer useful */ (*env)->DeleteLocalRef(env, localRefCls); 14 /* Is the global reference created successfully? */ 16 if (stringClass == NULL) { return NULL; /* out of memory exception thrown */ 18 } } 20 ... // potreba explicitne mazat 22 if (terminate) { (*env)->DeleteGlobalRef(env, stringClass); 24 } Zdroj: JNI Book 37/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a JNI Použití monitorů ◾ vždy monitor uvolnit synchronized (obj) { 2 ... // synchronized block } 1 if ((*env)->MonitorEnter(env, obj) != JNI_OK) ...; ... 3 if ((*env)->ExceptionOccurred(env)) { ... /* exception handling */ 5 /* remember to call MonitorExit here */ if ((*env)->MonitorExit(env, obj) != JNI_OK) ...; 7 } ... /* Normal execution path. */ 9 if ((*env)->MonitorExit(env, obj) != JNI_OK) ...; Zdroj: JNI Book 38/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a JNI Wait/Notify ◾ generické volání metod (GetMethodID, CallVoidMethod) ◾ potřeba držet monitor 1 /* precomputed method IDs */ static jmethodID MID_Object_wait; 3 static jmethodID MID_Object_notify; static jmethodID MID_Object_notifyAll; 5 void 7 JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout) { (*env)->CallVoidMethod(env, object, MID_Object_wait, timeout); 9 } 11 void JNU_MonitorNotify(JNIEnv *env, jobject object) { 13 (*env)->CallVoidMethod(env, object, MID_Object_notify); } 15 void 17 JNU_MonitorNotifyAll(JNIEnv *env, jobject object) { (*env)->CallVoidMethod(env, object, MID_Object_notifyAll); 19 } Zdroj: JNI Book 39/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a JNI Explicitní získání JNIenv pro stávající vlákno ◾ např. callback volaný OS ◾ odkaz na JavaVM lze předávat mezi voláními a vlákny ◾ získání odkazu např. JNI_GetCreatedJavaVMs nebo GetJavaVM 1 JavaVM *jvm; /* already set */ 3 f() { JNIEnv *env; 5 (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL); ... /* use env */ 7 } Zdroj: JNI Book 40/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Vlákna a JNI Mapování vláknového modelu OS a JVM ◾ záleží, zda pro danou platformu JVM podporuje nativní vlákna ◾ závisí od dané platformy i od daného JVM Můžeme implementovat v Javě afinitu k CPU ◾ závisí od dané platformy i od daného JVM ◾ sched_setaffinity(gettid(), ...) ◾ v praxi může pro danou platformu dobře fungovat 41/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Futures & TPE v C++ Futures nabízí knihovna libboost http://www.boost.org ◾ Nabízí ± stejná primitiva jako pthread ◾ Navíc nabízí některé pokročilejší nástroje pro vlákna 1 #include #include 3 using namespace std; 5 void hello() 7 { cout << "Pocitam." << endl; 9 } 11 int main(int argc, char* argv[]) { 13 boost::thread thrd(&hello); thrd.join(); 15 return 0; } 42/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Futures & TPE v C++ Futures #include 2 #include #include 4 using namespace std; 6 string hello() 8 { cout << "FT: Pocitam." << endl; 10 sleep(5); cout << "FT: Vypocet hotov." << endl; 12 return "12345"; } 14 int main(int argc, char* argv[]) 16 { boost::packaged_task pt(hello); 18 boost::unique_future fi=pt.get_future(); 20 boost::thread task(boost::move(pt)); cout << "Master: vytvoreno vlakno." << endl; 22 cout << "Master: Cekam na vysledek." << endl; fi.wait(); 24 cout << "Mam vysledek: " << fi.get() << endl; return 0; 26 } 43/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Futures & TPE v C++ Samotná knihovna libboost nemá threadpools http://threadpool.sourceforge.net/ implementuje threadpools v C++ nad libboost 44/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Futures & TPE v C++     Anybody who tells me that STL and especially Boost are stable and portable is just so full of bullshit that it’s not even funny. – Linus Torvalds 45/45 Vlákna a GUI Vlákna a JNI Futures & TPE v C++ Ostatní Ostatní Odevzdávání ,,projektu‘‘ ◾ Odevzdavarny/Projekt ◾ UCO-Jmeno-Prijmeni.{zip,tgz,...} Organizace zkoušky ◾ diskuse řešení projektu ◾ minimálně 2 obecné otázky ◾ minimálně 1 specializační (Java/C/Ada)