RayPan
    • Create new note
    • Create a note from template
      • 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
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me 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
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control 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
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me 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
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2016q3 Homework2 (phonebook-concurrent) contributed by <`RayPan`> ## 開發環境 * Ubuntu 16.04 LTS * CPU ``` Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 2 Core(s) per socket: 2 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 60 Model name: Intel(R) Core(TM) i5-4200H CPU @ 2.80GHz Stepping: 3 CPU MHz: 2869.015 CPU max MHz: 3400.0000 CPU min MHz: 800.0000 BogoMIPS: 5587.49 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 3072K NUMA node0 CPU(s): 0-3 ``` --- ## 開發目標 * 學習第二週提及的 concurrency 程式設計 * 學習 POSIX Thread * 學習效能分析工具 * code refactoring 練習 * 探索 clz 的應用 --- ## POSIX Thread 參考資料: [Getting Started With POSIX Threads](http://www.csie.ntu.edu.tw/~r92094/c++/pthread.txt)、[Pthread程式撰寫](http://blog.xuite.net/nikoung/wretch/154071878-Pthread+%E7%A8%8B%E5%BC%8F%E6%92%B0%E5%AF%AB) 、[冼鏡光的並行計算投影片](http://blog.dcview.com/article.php?a=BTgBYw1qUWIAaQ%3D%3D)、[POSIX threads explained](http://www.ibm.com/developerworks/library/l-posix1/index.html) * #### Parallel vs. Concurrent 1. parallel : 兩件事齊頭並進 2. concurrent:電腦系統中,執行的程式數目通常比CPU(加上它們的核心,core)的數目大很多,因此作業系統的做法是讓這個程式使用CPU很短一段時間、再讓另一個程式使用很短一段時間等等(交錯執行)。因爲每一個程式佔用CPU的時間很短,很快又輪回同一個程式,所以對使用人而言可能完全沒有程式在斷斷續續執行的感覺,我們會覺得動作是連續的。 * #### Process/Thread 一個執行的程式(Process)可以分成幾條執行緒(thread),每一條執行緒處理賦予它的工作;ie,一個行程可以分成三條執行緒,一條負責讀取各種輸入、第二條處理視窗的輸出、第三條從事計算的工作,這三條執行線彼此之間相互協調配合、完成該程式的工作。 ![](https://i.imgur.com/TQq21Bb.jpg) ###### [圖片來源](https://hackpad.com/ep/pad/static/5TOjUJI2rKu) POSIX標準中支援的執行緒函式庫稱為pthread,我們可以透過pthread結構與pthread_create() 函數執行某個函數指標,以建立新的執行緒。 * #### pthread_create 建立一個thread並執行附帶的function ```clike pthread_t mythread; pthread_attr_t mythread_attribute; void thread_function(void *argument); char *some_argument; int pthread_create(&mythread, mythread_attribute, (void *)&thread_function,(void *)&some_argument); ``` 1.參數mythread為pthread的指標,在使用Thread之前必須要先宣告一個pthread_t的變數。 2.參數mythread_attribute為該Thread的屬性,預設是NULL,如果沒有其他特殊的需求直接填入NULL即可。 3.參數thread_function)(void *)為Function pointer,這邊要放入你要執行的Function。 4.參數void *some_argument為Function pointer所要帶的參數。 回傳值: 如果執行成功則回傳0,如果執行失敗則回傳錯誤代碼。 * #### pthread_join 暫停目前執行pthread_join的Thread,等到目標Thread執行完畢之後目前執行pthread_join的Thread才會繼續執行。 ```clike int pthread_join (pthread_t thread, void **value_ptr) ``` 1.參數:pthread_t thread為要等待的目標Thread。 2.參數:void **value_ptr用來取得目標Thread的回傳值。 回傳值: 如果執行成功則回傳0,如果執行失敗則回傳錯誤代碼。 * 簡單範例程式 ```c=1 /*thread1.c*/ #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> void *thread_function(void *arg) { int i; for ( i=0; i<20; i++ ) { printf("Thread says hi!\n"); sleep(1); } return NULL; } int main(void) { pthread_t mythread; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) { printf("error creating thread."); abort(); } if ( pthread_join ( mythread, NULL ) ) { printf("error joining thread."); abort(); } exit(0); } ``` 主程式中本身會產生一條執行緒,thread_create又建立一條新執行緒,所以這個程式包含兩條執行緒。 主程式的執行緒繼續執行下一個if(pthread_join....)的判斷 新建的執行緒結束時,會停止並等待被併入或加入其他執行緒。 pthread_create() 會分裂成兩條執行緒,pthread_join() 則會將兩條執行緒合併為一。 在 thread_function() 完成時,主程式的執行緒已經呼叫pthread_join(),當這個情況發生時,主程式的執行緒會 block住(go to sleep) 並等待 thread_function() 完成。 當thread_fuction()完成時,pthread_join()會回傳值,所以現在我們的程式又有一條主執行緒,所以必須好好處理每條新建的執行緒,否則達到系統的最大值執行緒數量限制會造成pthread_create錯誤。 ```clike=1 /*thread2.c*/ #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> int myglobal; void *thread_function(void *arg) { int i,j; for ( i=0; i<20; i++ ) { j=myglobal; j=j+1; printf("."); fflush(stdout); sleep(1); myglobal=j; } return NULL; } int main(void) { pthread_t mythread; int i; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) { printf("error creating thread."); abort(); } for ( i=0; i<20; i++) { myglobal=myglobal+1; printf("o"); fflush(stdout); sleep(1); } if ( pthread_join ( mythread, NULL ) ) { printf("error joining thread."); abort(); } printf("\nmyglobal equals %d\n",myglobal); exit(0); } ``` 執行結果 ``` o.o.o.o.o..o.o.o.o.o.o.o.oo..oo.o..o.oo. myglobal equals 21 ``` myglobal起始值為0,主執行緒與新執行緒各將myglobal增加20,照理說應該是40,但輸出21原因為何? 因為兩條執行緒同時執行,當thread_fucntion把j值寫回myglobal,他會複寫主執行緒中的修改。 解決上述問題 加入互斥鎖(mutex) ```c=1 /*thread3.c*/ #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> int myglobal; pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER; void *thread_function(void *arg) { int i,j; for ( i=0; i<20; i++ ) { pthread_mutex_lock(&mymutex); j=myglobal; j=j+1; printf("."); fflush(stdout); sleep(1); myglobal=j; pthread_mutex_unlock(&mymutex); } return NULL; } int main(void) { pthread_t mythread; int i; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) { printf("error creating thread."); abort(); } for ( i=0; i<20; i++) { pthread_mutex_lock(&mymutex); myglobal=myglobal+1; pthread_mutex_unlock(&mymutex); printf("o"); fflush(stdout); sleep(1); } if ( pthread_join ( mythread, NULL ) ) { printf("error joining thread."); abort(); } printf("\nmyglobal equals %d\n",myglobal); exit(0); } ``` 兩條執行緒不能同時被一個互斥鎖鎖住,如果執行緒A想要鎖一個mutex然而執行緒B已經鎖住該mutex,A會sleep。 當B釋出這個mutex(透過pthread_mutex_unlock()),A即可鎖住該mutex。 亦即當一個mutex被鎖住,所有想要鎖的執行緒會被queue up。 pthread_mutex_lock()以及pthread_mutex_unlock()確保一個資料結構同時只能被一個執行緒存取,用來保護該資料結構。 * fflush(stdout) : forces a write of all user-space buffered data for the given output or update stream via the stream's underlying write function. The open status of the stream is unaffected. stdout是標準輸出。有時候,我們輸出到stdout的內容不能及時輸出,因為stdout的緩衝區沒有滿或者其他原因,fflush(stdout)就是強迫把stdout內容輸出並清空stdout。 --- ## 記憶體映射函數 mmap 參考資料:[記憶體映射函數 mmap](http://welkinchen.pixnet.net/blog/post/41312211-%E8%A8%98%E6%86%B6%E9%AB%94%E6%98%A0%E5%B0%84%E5%87%BD%E6%95%B8-mmap-%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) * #### 簡介 把文件內容映射到一段記憶體上(準確說是虛擬記憶體上),通過對這段記憶體的讀取和修改,實現對文件的讀取和修改。 * #### 函數 ```clike void *mmap(void *start,size_t length, int prot, int flags, int fd, off_t offsize); ``` 1.參數start:指向欲映射的核心起始位址,通常設為NULL,代表讓系統自動選定位址,核心會自己在行程位址空間中選擇合適的位址建立映射。 映射成功後返回該位址。如果不是NULL,則給核心一個提示,應該從什麼位址開始映射,核心會選擇start之上的某個合適的位址開始映射。 建立映射後,真正的映射位址通過返回值可以得到。 2.參數length:代表映射的大小。將文件的多大長度映射到記憶體。 3.參數prot:映射區域的保護方式。 可以為以下幾種方式的組合: `PROT_EXEC` 映射區域可被執行 `PROT_READ` 映射區域可被讀取 `PROT_WRITE` 映射區域可被寫入 `PROT_NONE` 映射區域不能存取 4.參數flags:影響映射區域的各種特性。在調用mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。 MAP_FIXED 如果參數start所指的位址無法成功建立映射時,則放棄映射,不對位址做修正。通常不鼓勵用此旗標。 MAP_SHARED 允許其他映射該文件的行程共享,對映射區域的寫入數據會複製回文件。 MAP_PRIVATE 不允許其他映射該文件的行程共享,對映射區域的寫入操作會產生一個映射的複製(copy-on-write),對此區域所做的修改不會寫回原文件。 MAP_ANONYMOUS 建立匿名映射。此時會忽略參數fd,不涉及文件,而且映射區域無法和其他進程共享。 MAP_DENYWRITE 只允許對映射區域的寫入操作,其他對文件直接寫入的操作將會被拒絕。 MAP_LOCKED 將映射區域鎖定住,這表示該區域不會被置換(swap)。 5.參數fd:由open返回的文件描述符,代表要映射到核心中的文件。如果使用匿名核心映射時,即flags中設置了MAP_ANONYMOUS,fd設為-1。 有些系統不支持匿名核心映射,則可以使用fopen打開/dev/zero文件,然後對該文件進行映射,可以同樣達到匿名核心映射的效果。 6.參數offset:從文件映射開始處的偏移量,通常為0,代表從文件最前方開始映射。 offset必須是分頁大小的整數倍(在32位體系統結構上通常是4K)。 * #### 步驟 1.用open系統調用打開文件, 並返回描述符fd. 2.用mmap建立記憶體映射, 並返回映射開始的地址指標start. 3.對映射(文件)進行各種操作 4.用munmap(void *start, size_t lenght)關閉記憶體映射. 5.用close系統調用關閉文件fd. --- ## 程式理解 首先將原始字典檔`DICT_FILE`輸出為一個「每筆資料長度為MAX_LAST_NAME_SIZE,剩餘補`\0`」的align.txt檔。 利用open函式打開文件檔align.txt,存取到fd ```c=55 int fd = open(ALIGN_FILE, O_RDONLY | O_NONBLOCK); ``` O_RDONLY:以只能讀取方式打開文件 O_NONBLOCK 以不可阻斷的方式打開文件,也就是無論有無數據讀取或等待,都會立刻返回行程之中。 利用mmap函式將fd映射到指標map所指到的記憶體空間。 ```c=78 char *map = mmap(NULL, fs, PROT_READ, MAP_SHARED, fd, 0); ``` 由於程式切成THREAD_NUM個執行緒執行 以下將他們接起來 ```c=114 for (int i = 0; i < THREAD_NUM; i++) { if (i == 0) { pHead = app[i]->pHead->pNext; dprintf("Connect %d head string %s %p\n", i, app[i]->pHead->pNext->lastName, app[i]->ptr); } else { etmp->pNext = app[i]->pHead->pNext; dprintf("Connect %d head string %s %p\n", i, app[i]->pHead->pNext->lastName, app[i]->ptr); } etmp = app[i]->pLast; dprintf("Connect %d tail string %s %p\n", i, app[i]->pLast->lastName, app[i]->ptr); dprintf("round %d\n", i); } ``` `assert(argc == 4 && "./main orgfName modfName Padded")`:若符合條件式則程式可以繼續執行;否則會秀出維護錯誤訊息的字串,並結束程式。 --- ## 使用Thread Pool達到效能改善 參考資料:[基於pthread實現的簡單threadpool](http://blog.csdn.net/jcjc918/article/details/50395528)、[wikipedia](https://en.wikipedia.org/wiki/Thread_pool) * ### Threadpool 有一組預先生成的thread,有個管理員來管理和調度這些thread,方法為管理員管理一個任務佇列(job queue),如果接收到新的任務就將其加到佇列尾,每個thread盯著佇列,如果佇列不是空的,就去佇列頭拿一個任務來處理(每個任務只能被一個thread拿到),處理完了就繼續去佇列取任務。 如果沒有任務,thread就會休眠,直到任務佇列不是空的。 如果這個管理員夠聰明,他可能會在沒有任務或任務少的時候,減少thread數,任務過多時增加thread數,實現資源的動態管理。 ![](https://i.imgur.com/P3M4JPO.jpg) Thread Pool 本身,他必須存放所有的 Thread 與任務佇列。 ### Thread pool 簡單實現 在建立threadpool的時候,預先指定thread的數量,然後去job queue取添加進來的task進行處理就好。 主要定義兩個結構 1.`threadpool_task_t` (in threadpool.c) 用於保存一個等待執行的task,包含要執行的函數及其參數。 ```c=53 typedef struct { void (*function)(void *); void *argument; } threadpool_task_t; ``` 2.`threadpool_t` (in threadpool.c) ```c=73 struct threadpool_t { pthread_mutex_t lock; //互斥鎖 pthread_cond_t notify; //條件變數 pthread_t *threads; //執行續陣列的起始指標 threadpool_task_t *queue; //任務佇列的起始指標 int thread_count; //執行緒數量 int queue_size; //任務佇列長度 int head; //目前任務佇列頭 int tail; //目前任務佇列尾 int count; //目前等待執行的任務數 int shutdown; //threadpool當前狀態是否為關閉 int started; //正在執行的thread數 }; ``` 函數 ###### 對外接口 * `threadpool_create` :建立threadpool。 ```c=91 threadpool_t *threadpool_create(int thread_count, int queue_size, int flags) ``` * `threadpool_add` :添加需要執行的任務。第二個參數為對應函數指標,第三個函數為對應函數參數。 ```c=149 int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument, int flags) ``` * `threadpool_destory` :銷毀存在的threadpool,若flad=1 為graceful結束,即等待全部做完才結束;若flag=0為immediate結束,即立即結束。 ```c=199 int threadpool_destroy(threadpool_t *pool, int flags) ``` ###### 內部輔助函數 * `threadpool_free`:釋放threadpool所申請的記憶體空間。 ```c=243 int threadpool_free(threadpool_t *pool) ``` * `threadpool_thread`:thread pool每個thread所執行的函數。 ```c=266 static void *threadpool_thread(void *threadpool) ``` --- 程式執行後效能比較圖 ![](https://i.imgur.com/JEJhUPJ.png) --- ## 筆記 參考資料: [Concurrency系列](http://enginechang.logdown.com/posts/784600-concurrency-series-1-the-road-to-understand-concurrency) * #### Evaluation求值 包括value computation:對一串運算式計算之結果;side effect:對物件修改的狀態。 * #### Sequenced-before 對同一個thread下,求值順序關係的描述。 * 若A sequenced before B: 代表A求值會先完成才會對B求值。 * `i++`後置運算子,value computation會先於side effect。 * `++i`前置運算子,則相反。 * #### Synchronizes-With 發生在兩個不同thread間的同步行為,當A synchronized-with B的時,代表A對記憶體操作的效果,對於B是可見的。而A和B是兩個不同的thread的某個操作。 * #### Sequential Consistency 1.對於每個獨立的處理單元,執行時都維持程式的順序(Program Order) 2.整個程式以某種順序在所有處理器上執行 --- 作業詳細資訊[A06:phonebook-concurrent](https://hackmd.io/s/rJsgh0na) ###### tags `RayPan` `A06:phonebook-concurrent`

    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