Dydaktyka
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Ćwiczenia 10, grupa śr. 12-14, 4 stycznia 2023 ###### tags: `PRW22` `ćwiczenia` `pwit` ## Deklaracje Gotowość rozwiązania zadania należy wyrazić poprzez postawienie X w odpowiedniej kolumnie! Jeśli pożądasz zreferować dane zadanie (co najwyżej jedno!) w trakcie dyskusji oznacz je znakiem ==X== na żółtym tle. **UWAGA: Tabelkę wolno edytować tylko wtedy, gdy jest na zielonym tle!** :::danger | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | ----------------------:| ----- | --- | --- | --- | --- | --- | --- | --- | --- | Michał Bronowicki | | | | | | | | | | Wiktor Bukowski | X | X | X | X | X | X | X | | | Jan Dalecki | X | | X | X | X | ==X== | X | X | | Mikołaj Depta | X | X | X | X | X | X | X | | | Kamil Galik | | | | | | | | | | Bartosz Głowacki | | | | | | | | | | Michał Hetnar | x | x | x | x | x | x | x | | | Adam Jarząbek | | | | | | | | | | Michał Kierul | | | | | | | | | | Mateusz Kisiel | X | X | X | X | X | | | | | Maksymilian Komarnicki | X | | X | X | X | X | X | X | | Julia Matuszewska | X | X | | X | X | X | | | | Andrzej Morawski | | | | | | | | | | Wojciech Pokój | | | X | X | X | X | | | | Marcin Sarnecki | X |==X==| X | X | X | | | | | Bartosz Szczeciński | X | X | X | X | X | X | X | | | Tomasz Wołczański | X | | X | X | X | X | X | | | Marcin Wróbel | X | X | X | X | X | X | X | X | | ::: :::info **Uwaga:** Po rozwiązaniu zadania należy zmienić kolor nagłówka na zielony. ::: ## Zadanie 1 :::success Autor: Mateusz Kisiel ::: ![](https://i.imgur.com/AtvutV7.png) Mamy wielopoziomową hierarchię pamięci podzieloną na clustery zwierające w sobie procesory. Np. dwupoziomową i do pierwszego poziomu odwołujemy się szybko, a operacje na drugim poziomie są bardziej kosztowne. W takim przypadku warto zastąpić BOLock(Back-off Lock) poprzez HBOLock. HBOLock to rozszrzenie idei Back-off Lock ktora polegała na tym, aby wątek robił coraz dłuższego sleepa w pętli w funkcji lock. Dzięki temu wątki oczekujące na dany zasób nie będą spamować zpytaniami o stan zmiennej i zmniejszy się użycie procesora. Rozszerzenie w HBOLock polega na uwzględnieniu tego, że odpytanie o stan zmiennej niesie za sobą różny koszt czasowy w zalezności od tego na którym poziomie hierarchi była ta zmienna. Chcemy, aby kosztownych zapytań było jak najmniej. HBOLock z tego powodu będzie zwiększać czas sleepa w wątkach z innego clusteru bardziej niż w wątkach w obrębie tego samego clusteru. ```java= public class HBOLock implements Lock { private static final int LOCAL_MIN_DELAY = ...; private static final int LOCAL_MAX_DELAY = ...; private static final int REMOTE_MIN_DELAY = ...; private static final int REMOTE_MAX_DELAY = ...; private static final int FREE = -1; AtomicInteger state; public HBOLock() { state = new AtomicInteger(FREE); } public void lock() { int myCluster = ThreadID.getCluster(); Backoff localBackoff = new Backoff(LOCAL_MIN_DELAY, LOCAL_MAX_DELAY); Backoff remoteBackoff = new Backoff(REMOTE_MIN_DELAY, REMOTE_MAX_DELAY); while (true) { if (state.compareAndSet(FREE, myCluster)) { return; } int lockState = state.get(); if (lockState == myCluster) { localBackoff.backoff(); } else { remoteBackoff.backoff(); } } } public void unlock() { state.set(FREE); } } ``` ## Zadanie 2 :::success Autor: Marcin Sarnecki ::: ![](https://i.imgur.com/r6eO4GW.png) Pomysł polega na uzyciu wielu zamków na różnych poziomach hierarchii pamięci. Każda kohorta wątków ma swój własny zamek oraz kohorty mają wspólny, globalny zamek. Wątek utrzymuje lock() jeśli utrzymuje zarówno lokalny, jak i globalny zamek Wątek najpierw próbuje zablokować lokalny zamek, a następnie upewnia się, że kohorta, do której należy, zablokowała globalny zamek. Przy odblokowaniu wątek najpierw sprawdza, czy inny wątek z jego kohorty nie próbuję zablokować zamka. Jeśli tak, to przepuszcza go, nie zwalniając globalnego zamka (występuje limit na liczbę takich sytuacji). Jeśli kohorta była pusta, to zwalnia oba zamki. Metoda `alone()` zwraca fałsz, jeśli $na \space pewno$ inny wątek z tej samej kohorty próbuje dostać się do zamka. Nie działa to w drugą stronę - jeśli zwróci prawdę, to może istnieć wątek próbujący dostać się do zamka, jednak takie przypadki są rzadkie.![](https://i.imgur.com/oDGu0Dk.png) `TurnArbiter` służy niezagłodzeniu innych kohort ![](https://i.imgur.com/3FetSDA.png) ## Zadanie 3 :::success Autor: Wojciech Pokój ::: ![](https://i.imgur.com/lXQRjB3.png) ![](https://i.imgur.com/wgQzBSc.png) ```java= public boolean contains(T item) { Node pred, curr; int key = item.hashCode(); lock.lock(); try { pred = head; curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } if (key == curr.key) { return true; } else { return false; } } finally { lock.unlock(); } } ``` :::info Na przykładzie zbioru zaimplementowanego przy pomocy CoarseList wyjaśnij, czym są niezmiennik reprezentacji (ang. representation invariant) oraz mapa abstrakcji (ang. abstraction map). ::: ![](https://i.imgur.com/7L446vg.png) W skrócie: - niezmiennik reprezentacji to pewien warunek który musi istnieć przed wywołaniem metody i musi zostać przywrócony wraz z wyjściem z metody - mapa abstrackji - zbiór który jest otrzymywalny z zadanej listy - w przypadku CoarseList jest że element jest w zbiorze <=> gdy jest odnajdywalny z głowy listy :::info Przypomnij, jakie punkty linearyzacji należy wybrać w metodach add(), remove() i contains() by następująca mapa abstrakcji była poprawna: “element należy do zbioru ⇔ węzeł na liście, w którym znajduje się ten element jest osiągalny z węzła head”. ::: add(x) - linia 24 - przepięcie wskaźnika poprzednika remove(x) - linia 43 - j.w. contains(x) - zajęcie zamka :::info Pokaż, że mapa abstrakcji z poprzedniego punktu nie jest poprawna, jeśli metody będą linearyzowane w momencie zajęcia zamka. ::: Chcemy żeby punkt linearyzacji wyznaczał nam moment kiedy żadany efekt operacji miał swój efekt i żeby mapa abstracji uwzględniała zmianę W przypadku add i remove nie może to być moment zajęcia zamka, ponieważ w tym momencie żadna zmiana nie miała miejsca i dopiero nastąpi w przyszłości :::info Zmodyfikuj powyższą mapę abstrakcji tak, by dla metod linearyzowanych w momencie zajęcia zamka, CoarseList nadal była poprawną implementacją zbioru. ::: x należy do zbioru jeśli: - jest osiągalny z głowy listy i zamek nie jest zajęty przez metodę remove(x) lub - zamek jest zajęty przez metodę add(x) ## Zadanie 4 :::success Autor: Maksymilian Komarnicki ::: ```java= public boolean add(T item) { int key = item.hashCode(); head.lock(); Node pred = head; try { Node curr = pred.next; curr.lock(); try { while (curr.key < key) { pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } if (curr.key == key) { return false; } Node newNode = new Node(item); newNode.next = curr; pred.next = newNode; return true; } finally { curr.unlock(); } } finally { pred.unlock(); } } public boolean remove(T item) { Node pred = null, curr = null; int key = item.hashCode(); head.lock(); try { pred = head; curr = pred.next; curr.lock(); try { while (curr.key < key) { pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } if (curr.key == key) { pred.next = curr.next; return true; } return false; } finally { curr.unlock(); } } finally { pred.unlock(); } } ``` Metody add() oraz remove() są linearyzowalne, bo można wyznaczyć dla nich punkty linearyzacji, czyli momenty, w których funkcje "mają efekt". Punkty te są różne w zależności, czy wywołanie metody zakończy się sukcesem, czy porażką. Metoda add() ma efekt, bo możemy zapewnić, że po dodaniu elementu znajduje się on w zbiorze. Wynika to z podpięcia nowo utworzonego węzła z węzłami pred i curr, które znajdują się w zbiorze, co wynika z tego, że są osiągalne z głowy listy. Metoda remove() ma efekt, bo możemy zapewnić, że skasowany element znajdował się w zbiorze, bo był osiągalny z głowy listy. 1. Wywołanie add() zakończone sukcesem - linearyzowalne w momencie, gdy następny większy klucz jest zablokowany(linia 7 albo 13). 2. Wywołanie add() zakończone porażką - linearyzowalne w momencie, gdy węzeł z dodawaną wartością jest zablokowany(linia 7 albo 13). 3. Wywołanie remove() zakończone sukcesem - linearyzowalne w momencie, gdy poprzedni węzeł w stosunku do usuwanego jest zablokowany(linia 36 albo 42). 4. Wywołanie remove() zakończone porażką - linearyzowalne w momencie, gdy pierwszy węzeł zawierający większą wartość niż ten usuwany jest zablokowany(linia 36 albo 42). ## Zadanie 5 :::success Autor: Mikołaj Depta ::: ```java public boolean contains(T item) { int key = item.hashCode(); head.lock(); Node pred = head; try { Node curr = pred.next; curr.lock(); try { while (curr.key < key) { pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } // tutaj zamiana względem add / remove return curr.key == key; } finally { curr.unlock(); } } finally { pred.unlock(); } } ``` Ponieważ przechodzimy listę, zawsze trzymając zamki dla sąsiednich węzłów mamy gwarancję, że szukany węzeł nie zostanie wstawiony pomiędzy aktualnie rozpatrywaną parą. Nasza lista jest posortowana, musimy więc jedynie znaleźć pierwszy węzeł o kluczu niemniejszym od szukanego klucza i sprawdzić czy ten klucz i klucz szukany są tym samym. ## Zadanie 6 :::success Autor: Jan Dalecki ::: ![](https://i.imgur.com/jOOeZMi.png) Optymistyczna implementacja zakłada, że problemy synchronizacji pojawiają się dość rzadko. Z tego powodu wątki chcące wykonać operację na elementach listy nie zakładają zamków podczas przejścia przez listę. Zamki są zakładane dopiero na modyfikowane węzły listy. Zaletą takiego podejścia jest zmniejszenie opóźnienia jeżeli problemy z synchronizacją faktycznie nie wystąpią często. ![](https://i.imgur.com/s4zHrpH.png) ![](https://i.imgur.com/Z7FhP5U.png) ![](https://i.imgur.com/Q19AhkN.png) Zakładamy, że element, który chcielibyśmy usunąć znajduje się początkowo w zbiorze. Niech wątek $A$ stara się usunąć wierzchołek $v$. Aby metoda remove została zagłodzona `validate` powinno zawsze zwracać `fałsz`. + Zatrzymajmy $A$ w linii 33 przed próbą wzięcia zamków. + Załóżmy teraz, że wątek $B$ dodaje między $pred_A$ oraz $curr_A$ węzeł $w$. + Dajemy $A$ kontynuować i okazuje się, że validate zwróci fałsz. + W czasie gdy $A$ zaczyna pętlę while od nowa usuwamy węzeł $w$. ## Zadanie 7 :::success Autor: Wiktor Bukowski ::: ![](https://i.imgur.com/dGRt7Yu.png) Potrzebny nam będzie globalny licznik, którego stan będziemy zapisywać lokalnie na początku pętli `while(true)`. W miejscu metody `validate()` musimy zdobyć zamek na globalnym liczniku. Następnie sprawdzamy, czy jego wartość jest równa lokalnej. Jeśli nie, przechodzimy do kolejnego obiegu pętli `while(true)`. Jeśli tak, zwiększamy licznik o 1 i wykonujemy te same operacje co w oryginalnych metodach. Implementacja na przykładzie `add()`: ```java= Lock counterLock; volatile int counter = 0; public boolean add(T item) { ... int localCounter; while (true) { localCounter = counter; ... curr.lock(); try { counterLock.lock(); try { if (localCounter == counter) { ... counter += 1; } } finally { counterLock.unlock(); } } ... } } ``` ## Zadanie 8 :::success Autor: Marcin Wróbel ::: ![](https://i.imgur.com/5PTRZoF.png) W LazyList każdy węzeł może zostać oznaczony (marked) jako usunięty. Usuwanie elementu składa się z dwóch etapów: - usunięcie logiczne - oznaczenie odpowiedniego węzła jako usunięty - usunięcie fizyczne - przepięcie odpowiedniego wskaźnika Dzięki takej różnicy możemy zmodyfikować metodę contains() tak, aby była wait-free. add() jest taki sam jak w OptimisticList (wywoływana w add() metoda validate() jest zdefiniowana inaczej) ```java= public boolean add(T item) { int key = item.hashCode(); while (true) { Node pred = head; Node curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } pred.lock(); try { curr.lock(); try { if (validate(pred, curr)) { if (curr.key == key) { return false; } else { Node node = new Node(item); node.next = curr; pred.next = node; return true; } } } finally { curr.unlock(); } } finally { pred.unlock(); } } } ``` remove() jest prawie takie samo jak w OptimisticList, tylko została dodana linijka 15 ```java= public boolean remove(T item) { int key = item.hashCode(); while (true) { Node pred = head; Node curr = head.next; while (curr.key < key) { pred = curr; curr = curr.next; } pred.lock(); try { curr.lock(); try { if (validate(pred, curr)) { if (curr.key == key) { curr.marked = true; pred.next = curr.next; return true; } else { return false; } } } finally { curr.unlock(); } } finally { pred.unlock(); } } } ``` ```java= public boolean contains(T item) { int key = item.hashCode(); Node curr = head; while (curr.key < key) curr = curr.next; return curr.key == key && !curr.marked; } ``` ```java= private boolean validate(Node pred, Node curr) { return !pred.marked && !curr.marked && pred.next == curr; } ``` Czy można, bez straty poprawności, zmodyfikować metodę **remove()** klasy **LazyList** tak, by zajmowała tylko jeden zamek? Gdyby LazyList zajmował w metodzie remove() tylko zamek na elemencie 1. **usuwanym** (curr) to tuż przed przepięciem wskaźników w węźle poprzedzającym. Inny wątek mógłby usunąć węzeł poprzedzający i przepięcie wskaźników nie zmieni ścieżki z head do tail. Przykład: Załóżmy, że wątek B chce usunąć element b, wątek C chce usunąć element c. `-inf -> a -> b -> c -> d -> inf` Wątek B przepina wskaźniki usuwając element b ``` -inf → a ↓ b → c → d → inf ``` Wątek C przepina wskaźniki chcąc usunąć element b ``` -inf → a ↓ c → d → inf ↑ b ``` Choć metoda `C.remove(c)` zwróciła true, to element `c` nie został fizycznie usunięty (został usunięty logicznie wartość pola marked jest ustawiona na true). Chociaż mapa abstrakcji jest poprawna, to element C nigdy nie zostanie z tej listy usunięty (problem z wydajnością). Co najważniejsze nie będą działały wywołania metod add(b) (metoda validate(a, c) zawsze będzie zwracać fałsz i add(b) będzie wykonywać pętle while w nieskończoność). Dodatkowo nie będzie działać add( c ). Skoro kolejne wywołania niektórych metod nie będą działać, to implementacja będzie niepoprawna. 2. **poprzedzającym usuwany** (pred) Po zajęciu pred.lock(), wiemy, że add() w innym wątku nie popsuje nic ze wskaźnikami w tym miejscu, ponieważ nie zajmie zajętego zamka. Załóżmy, że wątek B chce usunąć element b, wątek C chce usunąć element c. `-inf -> a -> b -> c -> d -> inf` Wątek B zajął lock a Wątek C zajął lock b Oba wątki wykonują metodę validate i wszystko jest ok. Wątek B przepina wskaźniki ``` -inf → a ↓ b → c → d → inf ``` Wątek C przepina wskaźniki ``` -inf → a ↓ c → d → inf ↑ b ``` Choć metoda `C.remove(c)` zwróciła true, to element `c` nie został fizycznie usunięty (został usunięty logicznie wartość pola marked jest ustawiona na true). Chociaż mapa abstrakcji jest poprawna, to element C nigdy nie zostanie z tej listy usunięty (problem z wydajnością). Co najważniejsze nie będą działały wywołania metod add(b) (metoda validate(a, c) zawsze będzie zwracać fałsz i add(b) będzie wykonywać pętle while w nieskończoność). Dodatkowo nie będzie działać add( c ). Skoro kolejne wywołania niektórych metod nie będą działać, to implementacja będzie niepoprawna. ![](https://i.imgur.com/oZjg0Pi.png) ## Zadanie 9 :::success Autor: Mikołaj Depta ::: ![](https://i.imgur.com/mP2WtGF.png) ![](https://i.imgur.com/xTkg65W.png) ```java= public boolean contains(T item) { int key = item.hashCode(); while (true) { Entry pred = this.head; // sentinel node; Entry curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } try { // pred.lock(); curr.lock(); <- tego nie robimy if (validate(pred, curr)) { return (curr.key == key); } } finally { // always unlock pred.unlock(); curr.unlock(); } } } ``` Kontrprzykład: Funkcję `contains(x)` wykonuje sam wątek `A` do linijki 12 włącznie (`validate` jest ok). Następnie pojawia się nowy wątek `B`, który usuwa element `x` i kończy swoje działanie. Następnie wątek `C` wykonuje `contains(x)` sam, kończy swoje działanie i zwraca `false`. Wówczas wybudza się `A` i kontynuuje swoje działanie od linijki 13 i zwraca `true`. Takie wykonanie nie jest linearyzowalne. ![](https://i.imgur.com/U8FatJU.png) ```java= public boolean contains(T item) { int key = item.hashCode(); Node curr = head; while (curr.key < key) curr = curr.next; return curr.key == key /* && !curr.marked; <- tego nie robimy */ } ``` Pominięcie badania bitu mark powoduje, że nasz algorytm nie przestrzega mapy abstrakcji. Element powinien być uważany za usunięty ze zbioru, gdy jest albo usunięty fizycznie lub oznaczony przez bit mark (usunięty logicznie). Algorytm pomijający sprawdzanie tej wartości jest niepoprawny.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully