Try โ€‚โ€‰HackMD

mergesort-concurrent

github contributed by <Diana Ho>

tags: d0651 sys

ๆกˆไพ‹ๅˆ†ๆž

ๅฐๅ–ฎๅ‘ Linked List

ๅ…ฉๅคง้‡้ปž

  • ๆŽ’ๅบ็š„ๅฐ่ฑกๆ˜ฏ singly-linked list
  • ๅˆฉ็”จ POSIX Thread ่™•็†๏ผŒ้œ€่ฆไธ€ไฝตๆ“ไฝœ synchronization object

้ ๆœŸ็›ฎๆจ™

  • ไฝœ็‚บ concurrency ็š„ๅฑ•็คบๆกˆไพ‹
  • ๅญธ็ฟ’ POSIX Thread Programming๏ผŒ็‰นๅˆฅๆ˜ฏ synchronization object
  • ็‚บๆ—ฅๅพŒๆ•ˆ่ƒฝๅˆ†ๆžๅ’Œ scalability ็ ”็ฉถๅปบๆง‹ๅŸบ็คŽๅปบ่จญ
  • ๅญธ็ฟ’็จ‹ๅผๅ“่ณชๅˆ†ๆžๅ’Œ็›ธ้—œ็š„้–‹็™ผๅทฅๅ…ท

ไฝœๆฅญ่ฆๆฑ‚

  • ๅฐ‡ merge sort ็š„ๅฏฆๅšๆ”น็‚บๅฏๆŽฅๅ— phonebook-concurrent ็š„ 35 ่ฌ็ญ†่ณ‡ๆ–™่ผธๅ…ฅ็š„่ณ‡ๆ–™ๆช”
    • ๅญ—ๅ…ธๆช”่ณ‡ๆ–™้œ€่ฆไบ‹ๅ…ˆ็”จ sort -R ่™•็†้Ž
    • ๆ€่€ƒๅฆ‚ไฝ•ๅพ—ๅˆฐๅ‡ๅ‹ปๅˆ†ไฝˆ็š„ไบ‚ๆ•ธๆŽ’ๅˆ—๏ผŒไธฆไธ”่จญ่จˆ่‡ชๅ‹•ๆธฌ่ฉฆ็š„ๆฉŸๅˆถ
  • ็ ”็ฉถ thread pool ็ฎก็† worker thread ็š„ๅฏฆๅš๏ผŒๆๅ‡บๅฏฆๅšๅฑค้ข็š„ไธ่ถณ๏ผŒไธฆไธ”ๅƒ็…ง concurrent-ll๏ผŒๆๅ‡บ lock-free ็š„ๅฏฆๅš
  • ๅญธ็ฟ’ concurrent-ll (concurrent linked-list ๅฏฆไฝœ) ็š„ scalability ๅˆ†ๆžๆ–นๅผ๏ผŒ้€้Ž gnuplot ่ฃฝๅœ–ๆฏ”่ผƒ merge sort ๅœจไธๅŒๅŸท่กŒ็ท’ๆ•ธ้‡ๆ“ไฝœ็š„ๆ•ˆ่ƒฝ
    • ๆณจๆ„ๅˆฐ linked list ๆฏๅ€‹็ฏ€้ปž้…็ฝฎ็š„่จ˜ๆ†ถ้ซ”ๅพ€ๅพ€ๆ˜ฏไธ้€ฃ็บŒ๏ผŒๆ€่€ƒ้€™ๅฐๆ•ˆ่ƒฝๅˆ†ๆž็š„ๅฝฑ้Ÿฟ
  • ไธ€ไฝตๅ˜—่ฉฆ้‡ๆง‹ (refactor) ็ตฆๅฎš็š„็จ‹ๅผ็ขผ๏ผŒไฝฟๅพ—็จ‹ๅผๆ›ดๅฎนๆ˜“้–ฑ่ฎ€ๅ’Œ็ถญ่ญทใ€‚ๅปถ็บŒ A05: introspect๏ผŒไธๅชๆ˜ฏๅœจๅ…ฑ็ญ†ไธŠ็”จๆ–‡ๅญ—ๆๅ‡บ่‰ฏๆ€ง่ฉณ็›ก็š„ๆ‰น่ฉ•๏ผŒไนŸ่ฉฒๅๆ˜ ๅœจ็จ‹ๅผ็ขผ็š„่ฎŠ้ฉ
  • ๅ…ฑ็ญ†็š„ๅ…งๅฎนๅ„˜้‡็”จ GraphViz ่ฃฝไฝœ

POSIX Thread Programming

ๅƒ่€ƒๆฆ‚ๅฟต

Model

Designing Threaded Programs

  • Boss/Worker Model
    Boss ๆŽฅๆ”ถๅˆฐไธ€ๅ€‹ๆ–ฐ่ฆๆฑ‚ๆ™‚๏ผŒๅ‹•ๆ…‹ๅปบ็ซ‹ไธ€ๅ€‹ Worker ๅฎŒๆˆ่ฉฒ้ …่ฆๆฑ‚๏ผŒ้ฉ็”จๆ–ผไผบๆœๅ™จไธŠใ€‚ๅฏๆ–ผ็จ‹ๅผ้–‹ๅง‹ๆ™‚ๅ…ˆๅปบ็ซ‹ๅฅฝไธ€ๅฎšๆ•ธ้‡็š„ๅŸท่กŒ็ท’(Thread Pool)๏ผŒๆธ›ๅฐ‘ๅŸท่กŒๆ™‚ๆœŸ็š„่ฒ ๆ“”ใ€‚
  • Peer Model (Workcrew model)
    ็›ธ่ผƒๆ–ผ Boss/Worker ๆจกๅผ๏ผŒๆญคๆจกๅผ็š„ๆฏๅŸท่กŒ็ท’ๆ“ๆœ‰่‡ชๅทฑ็š„่ผธๅ…ฅ๏ผŒ้ฉ็”จๆ–ผ็Ÿฉ้™ฃ้‹็ฎ—ใ€่ณ‡ๆ–™ๅบซๆœๅฐ‹็ญ‰ใ€‚
  • Pipeline Model
    ๅŸท่กŒ็ท’็”ฑไธŠไธ€้šŽๆฎตๅ–ๅพ—่ผธๅ…ฅ่ณ‡ๆ–™๏ผŒไธฆๅฐ‡็ตๆžœๅ‚ณ่‡ณไธ‹ไธ€้šŽๆฎต็š„ๅŸท่กŒ็ท’ใ€‚ๆ•ด้ซ”่ผธๅ‡บๅ—ๅˆถๆ–ผๅŸท่กŒๆœ€ไน…็š„้šŽๆฎต๏ผŒ้ ˆๆณจๆ„ๆฏไธ€้šŽๆฎต็š„ๅทฅไฝœ่ฒ ๆ“”่ฆ็›กๅฏ่ƒฝๅนณๅ‡ใ€‚

ๅƒ่€ƒๆฆ‚ๅฟต

manager-worker

ๆžถๆง‹







hierarchy



Manager

Manager



Worker1

Worker1



Manager->Worker1





Worker2

Worker2



Manager->Worker2





Worker3

Worker3



Manager->Worker3





็‚บไบ†ๅˆ†้…ๅทฅไฝœ๏ผŒๅœจ worker thread ๅฏฆไฝœ Task ็š„ๆฉŸๅˆถใ€‚ๆฏๅ€‹ไธป่ฆ็š„ๆ“ไฝœๆœƒๅ…ˆ่ขซๆ”พ้€ฒ task queue ่ฃก้ ญ๏ผŒ็ฉบ้–’็š„ thread ๅ†ๅพž task queue ่ฃก้ ญๆๅ– task ๅŸท่กŒ๏ผŒๅช่ฆๆŠŠๆˆ‘ๅ€‘็š„ๆ“ไฝœๅฏซๆˆ task๏ผŒๅฐฑ่ƒฝ้ †ๅˆฉๅŸท่กŒใ€‚







%0



้–‹ๅง‹Process

้–‹ๅง‹Process



ๆๅ–Task

ๆๅ–Task



้–‹ๅง‹Process->ๆๅ–Task





ๆ˜ฏๅฆ็‚บ็ตๆŸTask

ๆ˜ฏๅฆ็‚บ็ตๆŸTask



้‡็™ผ็ตๆŸTask

้‡็™ผ็ตๆŸTask



ๆ˜ฏๅฆ็‚บ็ตๆŸTask->้‡็™ผ็ตๆŸTask


ๆ˜ฏ



ๅŸท่กŒTask

ๅŸท่กŒTask



ๆ˜ฏๅฆ็‚บ็ตๆŸTask->ๅŸท่กŒTask


ๅฆ



็ตๆŸProcess

็ตๆŸProcess



ๆๅ–Task->ๆ˜ฏๅฆ็‚บ็ตๆŸTask





้‡็™ผ็ตๆŸTask->็ตๆŸProcess





ๅŸท่กŒTask->ๆๅ–Task





ๅƒ่€ƒๅœ–็‰‡

Synchronization

synchronization object

  • pthread_join๏ผš ๆŸๅŸท่กŒ็ท’ๆšซๅœๅŸท่กŒ็›ดๅˆฐๅฆไธ€็›ด่กŒ็ท’็ตๆŸ

  • Mutex๏ผš ๅŒไธ€ๆ™‚้–“ๅ…งๅชๆœ‰ไธ€ๅŸท่กŒ็ท’ๅฏไปฅไฟๆœ‰่ฉฒ lock ๅŠไฟ่ญท่ณ‡ๆ–™็š„ๅญ˜ๅ–ๆฌŠ

    • Initialize
      • Static๏ผšPTHREAD_MUTEX_INITIALIZER
      • Dynamic๏ผšpthread_mutex_init()
    • Lock
      • pthread_mutex_lock()
    • Release
      • pthread_mutex_unlock()
  • Condition variable๏ผš ๅฐ‡ไบ‹ไปถๅฏฆ่ณชๅŒ–๏ผŒไธฆๆไพ›ๅ‡ฝๅผๅ–š้†’็ญ‰ๅพ…่ฉฒไบ‹ไปถ็š„ๅŸท่กŒ็ท’

    • Initialize
      • Static๏ผšPTHREAD_COND_INITIALIZER
      • Dynamic๏ผšpthread_cond_init()
    • Waits for condition variables
      • ็ญ‰ๅพ…็›ดๅˆฐๅ–š้†’๏ผšpthread_cond_wait()
      • ็ญ‰ๅพ…็‰นๅฎšๆ™‚้–“๏ผšpthread_cond_timewait()
    • ๅ–š้†’็ญ‰ๅพ…ไธญๅŸท่กŒ็ท’
      • ๅ–š้†’ๅ…ถไธญไน‹ไธ€๏ผšpthread_cond_signal()
      • ๅ–š้†’ๆ‰€ๆœ‰็ญ‰ๅพ…ไธญๅŸท่กŒ็ท’๏ผšpthread_cond_broadcast()
    • ็‚บ้ฟๅ… Spurious Wake Ups ๆˆ–ๅ…ถไป–ๅ…ˆ่ขซๅ–š้†’็š„ๅŸท่กŒ็ท’ๅŸท่กŒๅฐŽ่‡ดๆขไปถ่ฎŠๆ•ธๅ†ๆฌกไธๆˆ็ซ‹๏ผŒไธ€่ˆฌๆœƒไปฅ่ฟดๅœˆๅˆคๆ–ทๆขไปถๆ˜ฏๅฆๆˆ็ซ‹๏ผŒไธๆˆ็ซ‹ๅ‰‡ๅ†ๆฌก้€ฒๅ…ฅ็ญ‰ๅพ…
  • pthread_once๏ผš ไฟ่ญ‰ๅˆๅง‹ๅ‡ฝๅผ่ขซ่จฑๅคš็›ด่กŒ็ท’ๅ‘ผๅซๆ™‚ๅƒ…ๅŸท่กŒไธ€ๆฌก

    • ๅฎฃๅ‘Š pthread_once_t ไธฆ้œๆ…‹ๅˆๅง‹็‚บPTHREAD_ONCE_INIT
    • ไปฅ pthread_once() ่ˆ‡ once ๅ€ๅŸŸๅ‘ผๅซ็›ฎๆจ™ๅ‡ฝๅผ
    • ไธๅฎน่จฑๅ‚ณ้žๅƒๆ•ธ่‡ณ once ๆ‰€ไฟ่ญท็š„ๅ‡ฝๅผ

Pthreads Management

Key

ไธ€็จฎๆŒ‡ๆจ™๏ผŒๅฐ‡่ณ‡ๆ–™ๅ’ŒๅŸท่กŒ็ท’้€ฒ่กŒ้—œ่ฏ

Thread cancellation

  • States๏ผš
    • PTHREAD_CANCEL_DISABLE
      • Type๏ผš ignored
    • PTHREAD_CANCEL_ENABLE
      • Types๏ผš
        • PTHREAD_CANCEL_ASYNCHRONOUS๏ผš็ซ‹ๅˆปๅ–ๆถˆ
        • PTHREAD_CANCEL_DEFERRED๏ผš็•ถๅŸท่กŒๅˆฐ Cancellation-point ๆ™‚็™ผ็”Ÿ
  • Cancellation-point
    • pthread_cond_wait(), pthread_cond_timewait(), pthread_join()
    • ไฝฟ็”จ่€…ๅฎš็พฉpthread_testcancel()
  • Asynchronous Cancellation๏ผš ็•ถๅŸท่กŒ็ท’ๅœจๆญค็‹€ๆ…‹ๆ™‚๏ผŒๅณไฝฟๅœจๅŸท่กŒๅ‡ฝๅผๅบซๅ‘ผๅซๆˆ–ๆ˜ฏ็ณป็ตฑๅ‘ผๅซๆ™‚ไนŸ่ƒฝๅค ่ขซๅ–ๆถˆ๏ผŒ้™ค้ž่ขซๅฎš็พฉ็‚บ cancellation-safe ๅฆๅ‰‡ๆ‡‰้˜ฒๆญข่ฉฒๆƒ…ๅฝข็™ผ็”Ÿ

mutex contention

mutrace ๅฏ็”จไพ†ๅตๆธฌ lock contention๏ผŒไฝฟ็”จๆ–นไพฟ๏ผŒไธ้œ€่ฆ้‡ๆ–ฐ็ทจ่ญฏ็จ‹ๅผ็ขผใ€‚

ๅƒ่€ƒๆฆ‚ๅฟต

  • Lock contention: this occurs whenever one process or thread attempts to acquire a lock held by another process or thread. The more fine-grained the available locks, the less likely one process/thread will request a lock held by the other. (For example, locking a row rather than the entire table, or locking a cell rather than the entire row.)

  • mutrace v.s. valgrind/drd

    In contrast to valgrind/drd it does not virtualize the CPU instruction set, making it a lot faster. In fact, the hooks mutrace relies on to profile mutex operations should only minimally influence application runtime. Mutrace is not useful for finding synchronizations bugs, it is solely useful for profiling locks.

  • About mutrace

    • mutrace is implemented entirely in userspace.
    • build your application with -rdynamic to make the backtraces mutrace generates useful.
    • -r| โ€“track-rt: checks on each mutex operation wheter it is executed by a realtime thread or not.
      =>use this to track down which mutexes are good candidates for priority inheritance.
    • matrace that can be used to track down memory allocation operations in realtime threads.

Measuring Lock Contention
More Mutrace


้–ฑ่ฎ€็จ‹ๅผ็ขผ

list.[ch]

list.h

  • node๏ผšๅ„ฒๅญ˜ๅฏฆ้š›็š„่ณ‡ๆ–™
typedef struct node { val_t data; struct node *next; } node_t;
  • linked list๏ผšๅปบ็ซ‹ linked list ้ †ไพฟ็ด€้Œ„้•ทๅบฆ๏ผŒๆ‰€ไปฅๅœจๅˆ†ๅ‰ฒ list ็š„ๆ™‚ๅ€™ๅช้œ€่ฆ่Šฑ
    O(N)
    ็š„ๆ™‚้–“ๅฐฑๅฅฝ๏ผŒไธ้œ€่ฆ้‡ๆ–ฐ่จˆ็ฎ—้•ทๅบฆใ€‚
typedef struct llist { node_t *head; uint32_t size; } llist_t;

intptr_t ่ณ‡ๆ–™ๅž‹ๆ…‹

When/Why to use intptr_t for type-casting in C?
What is the use of intptr_t?

  • ๅŠŸ่ƒฝ
    • ๅฏpointer่ฝ‰ๆ›ๆˆintegerๅž‹ๆ…‹๏ผŒๅšไฝๅ…ƒ้‹็ฎ—่ˆ‡ๅŠ ๆธ›ๆณ•ใ€‚
      ็›ธ่ผƒๆ–ผ void *๏ผŒintptr_t ็š„ไฝๅ€ๅฏไปฅๅš bitwise operation ่€Œ void * ไธ่ƒฝใ€‚ๅšไฝๅ…ƒ้‹็ฎ—๏ผŒ็„ก่™Ÿ็š„uintptr_tๆœƒๆฏ”่ผƒๅฅฝใ€‚
    โ€‹โ€‹โ€‹โ€‹/* Types for `void *' pointers. */ โ€‹โ€‹โ€‹โ€‹#if __WORDSIZE == 64 โ€‹โ€‹โ€‹โ€‹# ifndef __intptr_t_defined โ€‹โ€‹โ€‹โ€‹typedef long int intptr_t; โ€‹โ€‹โ€‹โ€‹# define __intptr_t_defined โ€‹โ€‹โ€‹โ€‹# endif โ€‹โ€‹โ€‹โ€‹typedef unsigned long int uintptr_t; โ€‹โ€‹โ€‹โ€‹#else โ€‹โ€‹โ€‹โ€‹# ifndef __intptr_t_defined โ€‹โ€‹โ€‹โ€‹typedef int intptr_t; โ€‹โ€‹โ€‹โ€‹# define __intptr_t_defined โ€‹โ€‹โ€‹โ€‹# endif โ€‹โ€‹โ€‹โ€‹typedef unsigned int uintptr_t; โ€‹โ€‹โ€‹โ€‹#endif
    • pointer ่ฝ‰ๆˆ integer ๆ™‚๏ผŒ32 bits ๆˆ– 64 bits ็š„ machine ๆœƒๆœ‰ pointer ้•ทๅบฆ็š„ๅทฎๅˆฅใ€‚่‹ฅไฝฟ็”จๅˆฐ intptr_t๏ผŒๅพ€ๅพŒๆˆ‘ๅ€‘ๅช้œ€ #include <stdint> ๅฐฑ่ƒฝๅ…้™ค้บป็…ฉ็š„ๅž‹ๆ…‹่ฝ‰ๆ›ใ€‚้™คๆญคไน‹ๅค–๏ผŒไนŸๆ˜ฏ็ขบไฟๅž‹ๆ…‹่ฝ‰ๆ›ไธŠไธๆœƒๅ‡บๅ•้กŒ็š„ไฟๅฎˆไฝœๆณ•ใ€‚
    โ€‹#ifdef MACHINE 64 โ€‹ typedef long intptr; โ€‹#else // MACHINE 32 โ€‹ typedef int intptr; โ€‹#endif
  • ็›ฎ็š„
    • ่ฎ“ node ไธญ็š„่ณ‡ๆ–™ๅฏไปฅๆ˜ฏๆŒ‡ๆจ™ๅž‹ๆ…‹๏ผŒไนŸๅฏไปฅๆ˜ฏไธ€่ˆฌ่ณ‡ๆ–™ๅž‹ๆ…‹ใ€‚
    • ็‚บไบ†็›ธๅฎนไธๅŒๅนณๅฐๆ‰€่จญ่จˆใ€‚

list.c

  • node_new๏ผšๅœจ next ๅ‰ๆ–นๆ”พๅ…ฅไธ€ๅ€‹ๆ–ฐ็š„ node๏ผŒไธฆๅ›žๅ‚ณๆŒ‡ๅ‘่ฉฒ node ไน‹ๆŒ‡ๆจ™
  • list_new๏ผšๅˆๅง‹ๅŒ–ไธ€ไธฒๆ–ฐ็š„ linked-list
  • list_add๏ผšๅœจ linked-list ็š„ๅฐพ็ซฏๅŠ ๅ…ฅไธ€ๅ€‹ๆ–ฐ็š„ node

threadpool.[ch]

threadpool.h

  • task๏ผš่ฎ“ thread ็Ÿฅ้“่‡ชๅทฑ่ฆๅšไป€้บผ
    ๆŽก็”จ็š„ doubly linked listใ€‚็ด€้Œ„ๅ‰ไธ€ๅ€‹ task ็š„็”จๆ„ๅœจๆ–ผ๏ผŒ็•ถๅพž queue ไธญๆ‹ฟๅ‡บๅทฅไฝœๅพŒ๏ผŒๅฏไปฅๅœจ
    O(1)
    ็š„ๆ™‚้–“ๅฐ‡ queue ็š„ tail ๆŒ‡ๅ‘ๆ‡‰ๆœ‰็š„ๅœฐๆ–นใ€‚
typedef struct _task { void (*func)(void *); void *arg; struct _task *next, *last; } task_t;
  • queue๏ผš่ฃก้ขๆ”พ็ฝฎ่‘—็ญ‰ๅพ…่ฆ่ขซ่™•็†็š„ task ๅ€‘
    ๅˆฉ็”จ mutex ไพ†็ขบไฟไธๆœƒๆœ‰ race condition ็š„็™ผ็”Ÿใ€‚
    ่€Œ condition variable ๅœจ่ง€็œ‹็š„ๆ™‚ๅ€™็™ผ็พๅชๆœ‰่ขซๅˆๅง‹ๅŒ–่€Œๅทฒ๏ผŒๆฒ’ๆœ‰ๅ…ถไป–ๅœฐๆ–นไฝฟ็”จๅˆฐใ€‚
typedef struct { task_t *head, *tail; pthread_mutex_t mutex; pthread_cond_t cond; uint32_t size; } tqueue_t;
  • thread pool๏ผšworker thread ็š„็ฎก็†ๅ“ก๏ผŒ่ฃก้ขๆ”พ็ฝฎไบ†ๆ‰€ๆœ‰ thread ๅ’Œ job queue
typedef struct { pthread_t *threads; uint32_t count; tqueue_t *queue; } tpool_t;

threadpool.c

  • task_free๏ผš้‡‹ๆ”พๅ–ฎไธ€ task ๅ…ˆๅ‰ๆ‰€้…็ฝฎ็š„่จ˜ๆ†ถ้ซ”็ฉบ้–“
  • tqueue_init๏ผšๅˆๅง‹ๅŒ– queue
  • tqueue_pop๏ผšๅฐ‡ queue ไธญ็š„ๅ–ฎไธ€ task ๅ–ๅ‡บ๏ผŒๆดพ้ฃ็ตฆ thread
  • tqueue_size๏ผšๅ›žๅ‚ณ queue ็•ถๅ‰็š„ๅคงๅฐ
  • tqueue_push๏ผšๅฐ‡ๆ–ฐ็š„ task ๅŠ ๅ…ฅ queue ไธญ
  • tqueue_free๏ผš้‡‹ๆ”พๅทฒ้…็ฝฎ็š„ queue ็ฉบ้–“
  • tpool_init๏ผšๅˆๅง‹ๅŒ– threadpool
  • tpool_free๏ผš้‡‹ๆ”พ threadpool ่จ˜ๆ†ถ้ซ”็ฉบ้–“

main.c

  • merge_list() ๅ‚ณๅ…ฅๅ…ฉๅ€‹list๏ผŒ็„ถๅพŒๅŽปๆฏ”่ผƒ2ๅ€‹listๆœ€ๅฐ็š„้‚ฃๅ€‹ๅ†ไธ€ไธ€ๆŒ‘ๅ‡บ๏ผŒๆ”พๅˆฐๆ–ฐ็š„list่ฃก้ข๏ผŒ็›ดๅˆฐๅ…ถไธญไธ€ๅ€‹list็š„ๆ•ธ่ขซๆŒ‘ๅฎŒ๏ผŒ้‚„ๆœ‰ๆ•ธๅญ—็š„listๅฐฑๆŽฅๅˆฐๅทฒๆŽ’ๅบๅฅฝ็š„listๅพŒ้ขใ€‚
  • merge_sort() ๅฆ‚ๆžœๅ‚ณๅ…ฅ็š„list sizeๅฐๆ–ผ2็›ดๆŽฅๅ›žๅ‚ณ่ฉฒlist๏ผŒๅฆๅ‰‡ๅฐฑๅฐ‡ไป–ๅพžไธญ้–“ๅˆ†ๆˆ2ๅ€‹listไน‹ๅพŒไธŸ้€ฒ merge_list() ๅšๆŽ’ๅบไธฆๅ›žๅ‚ณๆŽ’ๅบๅฅฝ็š„listใ€‚
  • merge ่ฃก้ขๅŽปๅˆคๆ–ท็พๅœจ็š„list็ธฝๅ…ฑไธฒไบ†ๅนพๅ€‹๏ผŒๅฆ‚ๆžœๅฐๆ–ผ่ผธๅ…ฅ็š„่ณ‡ๆ–™ๆ•ธ๏ผŒๅฐฑ็นผ็บŒๅšๆŽ’ๅบไธฒ่ตทไพ†๏ผŒไธ็„ถๅฐฑ่จญ็ฝฎ็ต‚ๆญขtask็„ถๅพŒๅฐๅ‡บๆŽ’ๅบๅฅฝ็š„listใ€‚
  • ็™ผ็พcut_func()่ทŸmerge_sort()้ƒฝๆ˜ฏๅ†ๅšๅˆ‡ๅ‰ฒlist๏ผŒ่€Œcut_func่ฃก้ขๅˆคๆ–ทcut_countๆ˜ฏๅฆๅฐๆ–ผmax_cutๅ…ถๅฏฆๆฒ’ๆœ‰ๅฟ…่ฆ๏ผŒๆ‰€ไปฅๆŠŠcut_count็ญ‰็›ธ้—œ่ฎŠๆ•ธๅˆชๆŽ‰๏ผŒไนŸๆŠŠmerge_sort()ๅˆชๆŽ‰๏ผŒ็•™ไธ‹cut_func()ๅฐฑๅฅฝใ€‚
  • task_run ๅฐ‡ๆˆ‘ๅ€‘่ฆๅš็š„ไบ‹ๅฏซๆˆtask๏ผŒๆ”พ้€ฒjob queue่ฃก๏ผŒ็ฉบ้–’็š„threadๆœƒๅพžqueue่ฃกๆ‹ฟtaskๅ‡บไพ†ๅšใ€‚

Makefile

ๅƒ่€ƒ

	-M 
	  ็”Ÿๆˆๆ–‡ไปถ้—œ่ฏ็š„ไฟกๆฏใ€‚ๅŒ…ๅซ็›ฎๆจ™ๆ–‡ไปถๆ‰€ไพ่ณด็š„ๆ‰€ๆœ‰ๆบไปฃ็ขผ
	-MM 
	  ๅ’Œ-Mไธ€ๆจฃ๏ผŒไฝ†ๆ˜ฏๅฎƒๅฐ‡ๅฟฝ็•ฅ็”ฑ#include<file>;้€ ๆˆ็š„ไพ่ณด้—œไฟ‚ใ€‚ 
	-MD 
	  ๅ’Œ-M็›ธๅŒ๏ผŒไฝ†ๆ˜ฏ่ผธๅ‡บๅฐ‡ๅฐŽๅ…ฅๅˆฐ.d็š„ๆ–‡ไปถ่ฃก้ข 
	-MMD 
	  ๅ’Œ-MM็›ธๅŒ๏ผŒไฝ†ๆ˜ฏ่ผธๅ‡บๅฐ‡ๅฐŽๅ…ฅๅˆฐ.d็š„ๆ–‡ไปถ่ฃก้ข	  
	-MF 
	 ๆŒ‡ๅฎš่ผธๅ‡บๅˆฐๆŸๅ€‹ๆช”ๆกˆ,ๅฆๅ‰‡้ ่จญๆ˜ฏๅŽŸๅง‹ๆช”ๆกˆๆช”ๅ.d

ๅƒ่€ƒ

	-rdynamic๏ผš
	 ๆŒ‡ๅฎš linker ๅฐ‡ๆ‰€ๆœ‰ label ๅฏซๅ…ฅๅˆฐ dynamic symbol tableใ€‚

็จ‹ๅผๅฏฆ้ฉ—

mutrace

$ (for i in {1..8}; do echo $RANDOM; done) | mutrace ./sort 4 8 mutrace: 0.2 sucessfully initialized for process sort (pid 11679). input unsorted data line-by-line sorted results: [300] [2022] [6225] [10989] [17303] [23622] [32251] [32551] mutrace: Showing statistics for process sort (pid 11679). mutrace: 3 mutexes used. mutrace: Showing 3 most contended mutexes: Mutex # Locked Changed Cont. tot.Time[ms] avg.Time[ms] max.Time[ms] Flags 0 145 40 30 0.023 0.000 0.001 Mx.--. 1 13 10 1 0.002 0.000 0.000 M-.--. 2 20 3 0 0.006 0.000 0.001 M-.--. |||||| /||||| Object: M = Mutex, W = RWLock /|||| State: x = dead, ! = inconsistent /||| Use: R = used in realtime thread /|| Mutex Type: r = RECURSIVE, e = ERRRORCHECK, a = ADAPTIVE /| Mutex Protocol: i = INHERIT, p = PROTECT / RWLock Kind: r = PREFER_READER, w = PREFER_WRITER, W = PREFER_WRITER_NONREC mutrace: Note that the flags column R is only valid in --track-rt mode! mutrace: Total runtime is 0.883 ms. mutrace: Results for SMP with 8 processors.
  • ่กจๆ ผ:
    • Locked: how often the mutex was locked during the entire runtime.
    • Changed: how often the owning thread of the mutex changed.
      =>If the number is high this means the risk of contention is also high.
    • Cont.: how often the lock was already taken when we tried to take it and we had to wait.
      ==> Mutex #1 is the most contended lock
    • tot.Time[ms]: for how long during the entire runtime the lock was locked
    • avg.Time[ms]: the average lock time
    • max.Time[ms]: the longest time the lock was held.
    • Flags: what kind of mutex this is (recursive, normal or otherwise).

ๆŒ‰็…งไฝœๆฅญ่ฆๆฑ‚ไธญๆ‰€ๆ•™็š„ไฝฟ็”จ addr2line ๆ‰พๅˆฐๅฏฆ้š›ๅฐๆ‡‰็š„็จ‹ๅผ่กŒๆ•ธ๏ผŒๅป้กฏ็คบ ??:0๏ผŒ ็›ฎๅ‰้‚„ๅœจๅฐ‹ๆ‰พๅŽŸๅ› 

$ addr2line -e sort 0x38 ??:0

UNIX ๆŒ‡ไปค็ต„ๅˆ

ๅฐ‡ phonebook-concurrent ่ฃก็š„ dictionary/words.txt ้€้Ž UNIX ๆŒ‡ไปคๆ‰“ไบ‚้ †ๅบ๏ผŒไน‹ๅพŒ้‡ๆ–ฐๅฐŽๅ‘ๅˆฐๅฆไธ€ๅ€‹ๆช”ๆกˆ input.txt๏ผŒ่ฎŠๆˆmerge sortๆ–ฐ็š„่ณ‡ๆ–™่ผธๅ…ฅ

$ uniq words.txt | sort -R > input.txt
  • sort๏ผšๅฐ‡่ผธๅ…ฅ่ณ‡ๆ–™ๆŽ’ๅบ
    • -R flag๏ผš้šจๆฉŸๆŽ’ๅบ
  • uniq๏ผšๅฏไปฅ็”จไพ†ๅฐ‡้‡่ค‡็š„่กŒๅˆช้™ค่€Œๅช้กฏ็คบไธ€ๅ€‹
    • -c flag๏ผš้กฏ็คบ้‡่ค‡ๅญ—่ฉž็š„ๅ‡บ็พๆฌกๆ•ธ
  • wc๏ผšๅˆ—ๅ‡บๆช”ๆกˆๆœ‰ๅคšๅฐ‘่กŒ๏ผŒๅคšๅฐ‘ๅ–ฎๅญ—๏ผŒๅคšๅฐ‘ๅญ—ๅ…ƒ
    • -l/w/m๏ผšๅƒ…ๅˆ—ๅ‡บ่กŒใ€ๅ–ฎๅญ—ใ€ๅญ—ๅ…ƒ

refactor

  • list.[ch]
    • list_add
      • return ๆฐธ้ ๆ˜ฏ 0ใ€‚
        ==> return list head ๅ›žๅ‚ณๅปบๅฅฝๅพŒๆ–ฐ็š„ llist ๆŒ‡ๆจ™๏ผŒไฝฟ็”จๆ›ดๅฝˆๆ€งใ€‚
      • ่จป่งฃๆๅˆฐ๏ผš่‹ฅๅ‚ณๅ…ฅ็š„ val ๅทฒ็ถ“ๅญ˜ๅœจ๏ผŒๅ‰‡ไธๅ†้‡ๆ–ฐๅปบ็ซ‹ๆ–ฐ็š„ node๏ผŒๅŽŸ็จ‹ๅผ็ขผไธญไธฆๆฒ’ๆœ‰็›ธๅฐๆ‡‰็š„ๆชขๆŸฅๆฉŸๅˆถใ€‚
      โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹llist_t list_add(llist_t *list, val_t val) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹{ โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ //check if value already exist โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ node_t *cur = list->head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ while(cur) { โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if(cur-> data == val) return list; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ cur = cur->next; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ } โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ //add to the head of the list โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ node_t *e = node_new(val, NULL); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ e->next = list->head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ list->head = e; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ list->size++; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return list; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹}
    • list_nth
      • index out of range ๆขไปถๅˆคๆ–ทๆœ‰่ชค๏ผŒๆŠŠ nth ็š„ n ็•ถไฝœ array ็š„ index๏ผŒๆŽ’ๅบ็‚บ 0 ~ list->size-1
        ==> ๅˆคๆ–ท out of range ๆขไปถๆ”น็‚บ if(idx > list->size-1)
        ==> ๅคšๅŠ ไธ€ๅ€‹ๅˆคๆ–ทif(idx == 0) return cur;
      โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹node_t *list_nth(llist_t *list, uint32_t idx) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹{ โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if (idx > list->size-1) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return NULL; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ node_t *head = list->head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if(idx == 0) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ while (idx--) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ head = head->next; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹}
  • threadpool.[ch]
    • tqueue_init๏ผš
      • ๆœช้€ฒ่กŒๆ‰€ๅ‚ณๅ…ฅ tqueue_t ็š„่จ˜ๆ†ถ้ซ”็ฉบ้–“้…็ฝฎ
        ==> ็›ดๆŽฅๅœจๅ‡ฝๅผไธญ็”จ malloc ้…็ฝฎ็ฉบ้–“๏ผŒๆˆๅŠŸๅ›žๅ‚ณ 0๏ผŒๅคฑๆ•—ๅ›žๅ‚ณ -1
      โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹int tqueue_init(tqueue_t *the_queue) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹{ โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ // if((the_queue = (tqueue_t *)malloc(sizeof(tqueue_t)) )== NULL) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ // return -1; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->head = NULL; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->tail = NULL; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_mutex_init(&(the_queue->mutex), NULL); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_cond_init(&(the_queue->cond), NULL); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->size = 0; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return 0; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹}
      ==> ๅพŒไพ†ๆŠŠ if((the_queue = (tqueue_t *)malloc(sizeof(tqueue_t)) )== NULL) return -1; ่จป่งฃๆŽ‰๏ผŒๆ‰ไธๆœƒ็™ผ็”Ÿ segmentation fault(core dumped)
    • tqueue_pop๏ผš
      • Queue ๆ‡‰่ฉฒๆ˜ฏ FIFO๏ผŒๅŽŸไพ†็จ‹ๅผ็ขผ pop ๅ‡บไพ†็š„ไธๆ˜ฏ้ ญ่€Œๆ˜ฏๅฐพ
        ==> ๆŠŠ tail ๆ”นๆˆ head
      โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹task_t *tqueue_pop(tqueue_t *the_queue) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹{ โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ task_t *ret; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_mutex_lock(&(the_queue->mutex)); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ ret = the_queue->head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if (ret) { โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->head = ret->next; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if (the_queue->head) { โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->head->last = NULL; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ } else { โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->tail = NULL; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ } โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->size--; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ } โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_mutex_unlock(&(the_queue->mutex)); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return ret; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹}
    • tqueue_push๏ผš
      • ๅ›žๅ‚ณๅ€ผๆฒ’ๆœ‰่™•็†้Œฏ่ชคๆƒ…ๆณ
        ==> ่‹ฅ task ไธๅญ˜ๅœจๅ‰‡ๅ›žๅ‚ณ -1๏ผŒๆˆๅŠŸ push ๅ‰‡ๅ›žๅ‚ณ 0
      • ๆ–ฐๅŠ ๅ…ฅ็š„ task ๆ‡‰่ฉฒ็ฝฎๆ–ผ queue ็š„ๅฐพ็ซฏ๏ผŒ้€™่ฃกๆ”พๅœจ้ ญ
        ==> ๅฐ‡ head ๆ”น็‚บ tail
      โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹int tqueue_push(tqueue_t *the_queue, task_t *task) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹{ โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if(task == NULL) return -1; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_mutex_lock(&(the_queue->mutex)); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ task->next = NULL; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ task->last = the_queue->tail; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if (the_queue->tail) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->tail->next = task; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->tail = task; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if (the_queue->size++ == 0) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->head = task; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_mutex_unlock(&(the_queue->mutex)); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return 0; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹}
    • tqueue_free๏ผš
      • ๅ›žๅ‚ณๅ€ผ็„กๆ„็พฉ
        ==> ๅˆคๆ–ท the_queue ๆ˜ฏๅฆๅญ˜ๅœจ็š„ๆƒ…ๆณ๏ผŒๅฆๅ‰‡ๅ›žๅ‚ณ -1๏ผŒไธฆไธ”ไธๅœจๅŸท่กŒไธ‹้ข้‡‹ๆ”พ่จ˜ๆ†ถ้ซ”้ƒจๅˆ†
      • condition variable ๆœ‰่ขซๅˆๅง‹๏ผŒไฝ†ๆ˜ฏๆฒ’ๆœ‰่ขซ destroy
        ==>thread_cond_detroy(&(the_queue->cond));
      โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹int tqueue_free(tqueue_t *the_queue) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹{ โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if(the_queue == NULL) return -1; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ task_t *cur = the_queue->head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ while (cur) { โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ the_queue->head = the_queue->head->next; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ free(cur); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ cur = the_queue->head; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ } โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_mutex_destroy(&(the_queue->mutex)); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ pthread_cond_destroy(&(the_queue->cond)); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return 0; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹}
    • tpool_free๏ผš
      • ๆญคๅ‡ฝๅผๅ›žๅ‚ณๅ€ผ็„กๆ„็พฉ
        ==> ๅขžๅŠ  the_pool ๆ˜ฏๅฆๅญ˜ๅœจ็š„ๅˆคๆ–ท๏ผŒไปฅๅŠpthread_join() ๆ˜ฏๅฆๆˆๅŠŸ็š„ๅˆคๆ–ท
      โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹int tpool_free(tpool_t *the_pool) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹{ โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if(the_pool == NULL) return -1; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ for (uint32_t i = 0; i < the_pool->count; ++i) โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ if(pthread_join(the_pool->threads[i], NULL) != 0) return -1; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ free(the_pool->threads); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ tqueue_free(the_pool->queue); โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹ return 0; โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹โ€‹}

ๅƒ่€ƒ
ๅƒ่€ƒ
ๅƒ่€ƒ
ๅƒ่€ƒ

thread pool

concurrent-ll
lock-free

่จญ่จˆ่‡ชๅ‹•ๆธฌ่ฉฆๅฏฆ้ฉ—

ๅœจ Makefile ไธญๅŠ ๅ…ฅ่ฆๅ‰‡๏ผŒๅˆฉ็”จ็ณป็ตฑๅ…งๅปบ็š„ๅทฅๅ…ทไพ†้šจๆฉŸ็”ข็”Ÿ่ณ‡ๆ–™ไปฅๅŠๆฏ”่ผƒ็ตๆžœใ€‚

ๅƒ่€ƒๅฏฆไฝœ

check: all sort -R words.txt | ./sort $(THREAD_NUM) $(shell wc -l words.txt) > sorted.txt diff -u words.txt sorted.txt && echo "Verified!" || echo "Failed!"

ๅ…ถไป–ๆ–นๆณ•

ๅƒ่€ƒๅฏฆไฝœ

Scalability

Scalability

  • Horizontal scalar (scale out/in)๏ผšๆ–ฐๅขž/ๆธ›ๅฐ‘็ฏ€้ปžๅˆฐ็ณป็ตฑไธญใ€‚ไพ‹ๅฆ‚๏ผšๅˆ†ๆ•ฃๅผ้‹็ฎ—๏ผŒ็”ฑๅคšๅฐ้›ป่…ฆๅ…ฑๅŒๅŸท่กŒไปปๅ‹™
  • Vertical scalar (scale up/down)๏ผšๅขžๅผท/ๆธ›ๅผฑๅ–ฎๅฐ้›ป่…ฆ็š„ๆ€ง่ƒฝใ€‚ไพ‹ๅฆ‚๏ผšไฝฟ็”จๅคšๆ ธๅฟƒ

็ ”็ฉถ concurrent-II ๅฆ‚ไฝ•่จˆ็ฎ— scalabilty

ๅƒ่€ƒๅฏฆไฝœ


Git Hooks

source

Customizing Git - Git Hooks