{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python - Multiprocessing and Threading\n",
    "\n",
    "- [GTWang - Python 多執行緒 threading 模組平行化程式設計教學](https://blog.gtwang.org/programming/python-threading-multithreaded-programming-tutorial/)\n",
    "- Introducing Python: Modern Computing in Simple Packages (p.269)\n",
    "- [Morvan - Threading](https://morvanzhou.github.io/tutorials/python-basic/threading/)\n",
    "- [Morvan - Multiprocessing](https://morvanzhou.github.io/tutorials/python-basic/multiprocessing/)\n",
    "- https://www.tutorialspoint.com/python/python_multithreading.htm\n",
    "- [A Better Model for Day to Day Threading Tasks](http://chriskiehl.com/article/parallelism-in-one-line/)\n",
    "- [Python多进程multiprocessing使用示例](http://outofmemory.cn/code-snippet/2267/Python-duojincheng-multiprocessing-usage-example)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Threading\n",
    "\n",
    "- `run()` − the entry point for a thread.\n",
    "- `start()` − starts a thread by calling the run method.\n",
    "- `join([time])` − waits for threads to terminate.\n",
    "- `isAlive()` − checks whether a thread is still executing.\n",
    "- `getName()` − returns the name of a thread.\n",
    "- `setName()` − sets the name of a thread."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import threading\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Active count: 5\n",
      "\n",
      "All Threads:\n",
      "[<_MainThread(MainThread, started 140736131847040)>, <Thread(Thread-2, started daemon 123145457262592)>, <Heartbeat(Thread-3, started daemon 123145462517760)>, <HistorySavingThread(IPythonHistorySavingThread, started 123145468846080)>, <ParentPollerUnix(Thread-1, started daemon 123145474101248)>]\n",
      "\n",
      "Current thread:\n",
      "<_MainThread(MainThread, started 140736131847040)>\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(f'Active count: {threading.active_count()}\\n')  # 獲取已激活的線程數\n",
    "print(f'All Threads:\\n{threading.enumerate()}\\n')  # 查看所有線程信息\n",
    "print(f'Current thread:\\n{threading.current_thread()}\\n')  # 查看現在正在運行的線程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def timer(orig_fun):\n",
    "    import time\n",
    "    import threading\n",
    "    \n",
    "    @wraps(orig_fun)\n",
    "    def wrapper(tic, *args, **kwargs):\n",
    "        print(f'{time.time() - tic:06.5f} - Start {orig_fun.__name__} at thread: {threading.current_thread()}')\n",
    "        orig_fun(tic, *args, **kwargs)\n",
    "        print(f'{time.time() - tic:06.5f} - Done {orig_fun.__name__}')\n",
    "        \n",
    "    return wrapper\n",
    "\n",
    "\n",
    "@timer\n",
    "def job_long(tic):\n",
    "    time.sleep(5)\n",
    "\n",
    "\n",
    "@timer\n",
    "def job_short(tic):  \n",
    "    time.sleep(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "def main():\n",
    "    tic = time.time()\n",
    "    print('Start main thread')\n",
    "    \n",
    "    # 建立一個子執行緒\n",
    "    td1 = threading.Thread(target=job_long, name='task1', kwargs={'tic': tic})\n",
    "    td2 = threading.Thread(target=job_short, name='task2', kwargs={'tic': tic})\n",
    "    \n",
    "    # 執行該子執行緒\n",
    "    td1.start()\n",
    "    td2.start()\n",
    "    \n",
    "    # 主執行緒繼續執行自己的工作\n",
    "    for i in range(3):\n",
    "        print(f\"{time.time() - tic:06.5f} - Main thread working...\", i)\n",
    "        time.sleep(1)\n",
    "\n",
    "    td1.join() # 附加到主線程 Wait until thread td1 terminates its task\n",
    "    td2.join() # 附加到主線程 Wait until thread td2 terminates its task\n",
    "    \n",
    "    print('\\nAll done!')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Start main thread\n",
      "0.00051 - Start job_long at thread: <Thread(task1, started 123145479356416)>\n",
      "0.00137 - Start job_short at thread: <Thread(task2, started 123145484611584)>\n",
      "0.00179 - Main thread working... 0\n",
      "1.00513 - Main thread working... 1\n",
      "2.00410 - Done job_short\n",
      "2.00619 - Main thread working... 2\n",
      "5.00270 - Done job_long\n",
      "\n",
      "All done!\n"
     ]
    }
   ],
   "source": [
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 多個子執行緒與參數"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thread 0\n",
      "Thread 1\n",
      "Thread 2\n",
      "Thread 3\n",
      "Thread 4\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "import threading\n",
    "import time\n",
    "\n",
    "# 子執行緒的工作函數\n",
    "def job(num):\n",
    "    print(\"Thread\", num)\n",
    "    time.sleep(1)\n",
    "\n",
    "# 建立 5 個子執行緒\n",
    "threads = []\n",
    "for i in range(5):\n",
    "    threads.append(threading.Thread(target = job, args = (i,)))\n",
    "    threads[i].start()\n",
    "\n",
    "# 主執行緒繼續執行自己的工作\n",
    "# ...\n",
    "\n",
    "# 等待所有子執行緒結束\n",
    "for i in range(5):\n",
    "    threads[i].join()\n",
    "\n",
    "print(\"Done.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 物件導向\n",
    "\n",
    "`threading.Thread` 在開始執行時，會呼叫它自己的 `run` 方法函數，這個方法函數預設會呼叫前面我們以 `target` 參數所指定的函數，在這裡我們在繼承 `threading.Thread` 類別之後，就直接把 `run` 覆寫成要執行的函數即可。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thread 0\n",
      "Thread 1\n",
      "Thread 2\n",
      "Thread 3\n",
      "Thread 4\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "import threading\n",
    "import time\n",
    "\n",
    "# 子執行緒類別\n",
    "class MyThread(threading.Thread):\n",
    "    def __init__(self, num):\n",
    "        threading.Thread.__init__(self)\n",
    "        self.num = num\n",
    "\n",
    "    def run(self):\n",
    "        print(f\"Thread {self.num}\")\n",
    "        time.sleep(1)\n",
    "\n",
    "# 建立 5 個子執行緒\n",
    "threads = []\n",
    "for i in range(5):\n",
    "    threads.append(MyThread(i))\n",
    "    threads[i].start()\n",
    "\n",
    "# 主執行緒繼續執行自己的工作\n",
    "# ...\n",
    "\n",
    "# 等待所有子執行緒結束\n",
    "for i in range(5):\n",
    "    threads[i].join()\n",
    "\n",
    "print(\"Done.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 佇列（Queue）\n",
    "\n",
    "如果我們有許多的工作要分給多個 CPU 核心做運算，最簡單的方式就是使用佇列的方式，讓多個 CPU 可從佇列中取得尚未處理的工作來處理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Worker 1: Data 0\n",
      "Worker 2: Data 1\n",
      "Worker 1: Data 2Worker 2: Data 3\n",
      "\n",
      "Worker 2: Data 4Worker 1: Data 5\n",
      "\n",
      "Worker 1: Data 6Worker 2: Data 7\n",
      "\n",
      "Worker 2: Data 8Worker 1: Data 9\n",
      "\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import threading\n",
    "import queue\n",
    "\n",
    "# Worker 類別，負責處理資料\n",
    "class Worker(threading.Thread):\n",
    "    def __init__(self, queue, num):\n",
    "        threading.Thread.__init__(self)\n",
    "        self.queue = queue\n",
    "        self.num = num\n",
    "\n",
    "    def run(self):\n",
    "        while self.queue.qsize() > 0:\n",
    "            # 取得新的資料\n",
    "            msg = self.queue.get()\n",
    "\n",
    "            # 處理資料\n",
    "            print(f\"Worker {self.num}: {msg}\")\n",
    "            time.sleep(1)\n",
    "\n",
    "# 建立佇列\n",
    "my_queue = queue.Queue()\n",
    "\n",
    "# 將資料放入佇列\n",
    "for i in range(10):\n",
    "    my_queue.put(f\"Data {i}\")\n",
    "\n",
    "# 建立兩個 Worker\n",
    "my_worker1 = Worker(my_queue, 1)\n",
    "my_worker2 = Worker(my_queue, 2)\n",
    "\n",
    "# 讓 Worker 開始處理資料\n",
    "my_worker1.start()\n",
    "my_worker2.start()\n",
    "\n",
    "# 等待所有 Worker 結束\n",
    "my_worker1.join()\n",
    "my_worker2.join()\n",
    "\n",
    "print(\"Done.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 鎖定（Lock）\n",
    "\n",
    "在平行化的多執行緒程式中，每個執行緒都是同時在執行的，若遇到不可以讓多個執行緒同時進行的工作時（例如將資料寫入同一個檔案），就必須使用鎖定（lock）的方式，一次只讓一個執行緒處理這種工作。在 Python 中，我們可以使用 threading 模組的 Lock 來處理多執行緒的鎖定問題。\n",
    "\n",
    "當一個執行緒呼叫了 Lock 的 acquire 時，代表取得了這個 Lock 的使用權，接著它就可以往下執行裡面的工作，若此時又有另外一個執行緒想要呼叫 acquire 取得使用權的話，就必須等待上一個執行緒執行完，並呼叫 release 釋放這個 Lock 之後，才能夠取得這個 Lock 的使用權，接著執行裡面的工作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lock acquired by Worker 1\n",
      "Worker 1: Data 0\n",
      "Lock released by Worker 1\n",
      "Lock acquired by Worker 2\n",
      "Worker 2: Data 1\n",
      "Lock released by Worker 2\n",
      "Lock acquired by Worker 1\n",
      "Worker 1: Data 2\n",
      "Lock released by Worker 1\n",
      "Lock acquired by Worker 1\n",
      "Worker 1: Data 4\n",
      "Lock released by Worker 1\n",
      "Lock acquired by Worker 2\n",
      "Worker 2: Data 3\n",
      "Lock released by Worker 2\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import threading\n",
    "import queue\n",
    "\n",
    "class Worker(threading.Thread):\n",
    "    def __init__(self, queue, num, lock):\n",
    "        threading.Thread.__init__(self)\n",
    "        self.queue = queue\n",
    "        self.num = num\n",
    "        self.lock = lock\n",
    "\n",
    "    def run(self):\n",
    "        while self.queue.qsize() > 0:\n",
    "            msg = self.queue.get()\n",
    "\n",
    "            # 取得 lock\n",
    "            self.lock.acquire()\n",
    "            print(f\"Lock acquired by Worker {self.num}\")\n",
    "\n",
    "            # 不能讓多個執行緒同時進的工作\n",
    "            print(f\"Worker {self.num}: {msg}\")\n",
    "            time.sleep(1)\n",
    "\n",
    "            # 釋放 lock\n",
    "            print(f\"Lock released by Worker {self.num}\")\n",
    "            self.lock.release()\n",
    "\n",
    "my_queue = queue.Queue()\n",
    "for i in range(5):\n",
    "    my_queue.put(f\"Data {i}\")\n",
    "\n",
    "# 建立 lock\n",
    "lock = threading.Lock()\n",
    "\n",
    "my_worker1 = Worker(my_queue, 1, lock)\n",
    "my_worker2 = Worker(my_queue, 2, lock)\n",
    "\n",
    "my_worker1.start()\n",
    "my_worker2.start()\n",
    "\n",
    "my_worker1.join()\n",
    "my_worker2.join()\n",
    "\n",
    "print(\"Done.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 旗標（Semaphore）\n",
    "\n",
    "旗標（semaphore）的作用跟鎖定（lock）類似，但是它多了一個計數器的功能，當一個執行緒呼叫了 acquire 時，旗標內部的計數器就會遞減 1，而當執行緒呼叫了 release 時，計數器就會遞增 1，當計數器遞減到 0 的時候，後面來的執行緒就要等待其他執行緒release 後才能繼續。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Semaphore acquired by Worker 1 (1 left)\n",
      "Worker 1: Data 0\n",
      "Semaphore acquired by Worker 2 (0 left)\n",
      "Worker 2: Data 1\n",
      "Semaphore released by Worker 2 (0 left)Semaphore released by Worker 1 (0 left)\n",
      "Semaphore acquired by Worker 2 (0 left)\n",
      "Worker 2: Data 3\n",
      "\n",
      "Semaphore acquired by Worker 1 (0 left)\n",
      "Worker 1: Data 4\n",
      "Semaphore released by Worker 2 (0 left)Semaphore released by Worker 1 (0 left)\n",
      "\n",
      "Semaphore acquired by Worker 3 (0 left)\n",
      "Worker 3: Data 2\n",
      "Semaphore released by Worker 3 (1 left)\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import threading\n",
    "import queue\n",
    "\n",
    "class Worker(threading.Thread):\n",
    "    def __init__(self, queue, num, semaphore):\n",
    "        threading.Thread.__init__(self)\n",
    "        self.queue = queue\n",
    "        self.num = num\n",
    "        self.semaphore = semaphore\n",
    "\n",
    "    def run(self):\n",
    "        while self.queue.qsize() > 0:\n",
    "            msg = self.queue.get()\n",
    "\n",
    "            # 取得旗標\n",
    "            self.semaphore.acquire()\n",
    "            print(f\"Semaphore acquired by Worker {self.num} ({self.semaphore._value} left)\")\n",
    "\n",
    "            # 僅允許有限個執行緒同時進的工作\n",
    "            print(f\"Worker {self.num}: {msg}\")\n",
    "            time.sleep(1)\n",
    "\n",
    "            # 釋放旗標\n",
    "            print(f\"Semaphore released by Worker {self.num} ({self.semaphore._value} left)\")\n",
    "            self.semaphore.release()\n",
    "\n",
    "my_queue = queue.Queue()\n",
    "for i in range(5):\n",
    "    my_queue.put(f\"Data {i}\")\n",
    "\n",
    "# 建立旗標\n",
    "maxconnections = 2\n",
    "semaphore = threading.Semaphore(maxconnections)\n",
    "\n",
    "my_worker1 = Worker(my_queue, 1, semaphore)\n",
    "my_worker2 = Worker(my_queue, 2, semaphore)\n",
    "my_worker3 = Worker(my_queue, 3, semaphore)\n",
    "\n",
    "my_worker1.start()\n",
    "my_worker2.start()\n",
    "my_worker3.start()\n",
    "\n",
    "my_worker1.join()\n",
    "my_worker2.join()\n",
    "my_worker3.join()\n",
    "\n",
    "print(\"Done.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Use Multiprocessing\n",
    "\n",
    "https://docs.python.org/3.6/library/multiprocessing.html\n",
    "\n",
    "> `multiprocessing` is a package that supports spawning processes using an API similar to the `threading` module. The `multiprocessing` package offers both local and remote concurrency, effectively side-stepping the **Global Interpreter Lock** by using subprocesses instead of threads. Due to this, the `multiprocessing` module allows the programmer to fully leverage multiple processors on a given machine. It runs on both Unix and Windows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "import multiprocessing\n",
    "import os\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def do_job(name):\n",
    "    whoami(name)\n",
    "    \n",
    "def whoami(name):\n",
    "    print(f'Process {os.getpid()} - doing: {name}')\n",
    "    \n",
    "def loopy(name):\n",
    "    whoami(name)\n",
    "    start = 1\n",
    "    n_loop = 100000\n",
    "    for i in range(start, n_loop):\n",
    "        print(f'Doing {i} of {n_loop}')\n",
    "        time.sleep(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Process 93907 - doing: Main program\n",
      "Process 93994 - doing: loop 0\n",
      "Process 93995 - doing: loop 1\n",
      "Process 93996 - doing: loop 2\n",
      "Process 93997 - doing: loop 3\n"
     ]
    }
   ],
   "source": [
    "# Do 4 times\n",
    "if __name__ == '__main__':\n",
    "    whoami('Main program')\n",
    "    for i in range(4):\n",
    "        p = multiprocessing.Process(target=do_job, args=(f'loop {i}',))\n",
    "        p.start()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Process 93907 - doing: Main program\n",
      "Process 94022 - doing: loopy\n",
      "Doing 1 of 100000\n",
      "Main processing...\n",
      "Doing 2 of 100000\n",
      "Doing 3 of 100000\n",
      "Doing 4 of 100000\n",
      "Doing 5 of 100000\n",
      "Main processing...\n",
      "Doing 6 of 100000\n",
      "Doing 7 of 100000\n",
      "Doing 8 of 100000\n",
      "Doing 9 of 100000\n",
      "Main processing...\n",
      "Doing 10 of 100000\n",
      "Doing 11 of 100000\n",
      "Doing 12 of 100000\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "# Early stop\n",
    "if __name__ == '__main__':\n",
    "    whoami('Main program')\n",
    "    p = multiprocessing.Process(target=loopy, args=(f'loopy',))\n",
    "    \n",
    "    # 在背景開始作業\n",
    "    p.start()\n",
    "    \n",
    "    # 主執行緒繼續執行自己的工作\n",
    "    for _ in range(3):\n",
    "        print('Main processing...')\n",
    "        time.sleep(4)\n",
    "    \n",
    "    # 提前終止\n",
    "    p.terminate()  # 如果沒有這行會一直等到 p 結束才會運行後面\n",
    "    print('Done.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 進程池 (Pool)\n",
    "\n",
    "[What's the difference between map_async and imap?](https://stackoverflow.com/a/26521507/3744499)\n",
    "\n",
    "- `Pool.map()`: 放入迭代参数，返回多个结果。Consumes your iterable by converting the iterable to a list.\n",
    "- `Pool.imap()`: It doesn't turn the iterable you give it into a list. It will iterate over the iterable one element at a time, and send them each to a worker process. \n",
    "- `Pool.map_async()`: \n",
    "- `Pool.apply`: Get result. 只能放入一组参数，并返回一个结果\n",
    "- `Pool.apply_async()`: 只能放入一组参数，并返回一个结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n",
      "4\n",
      "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "import multiprocessing as mp\n",
    "\n",
    "def job(x):\n",
    "    return x**2\n",
    "\n",
    "\n",
    "def multicore():\n",
    "    # Init a Pool\n",
    "    pool = mp.Pool(processes=3)  # 自定義核數量\n",
    "    \n",
    "    # 在 map() 中需要放入函數和需要迭代運算的值，然後它會自動分配給CPU核，返回結果\n",
    "    res = pool.map(job, range(10)); print(res)\n",
    "    \n",
    "    # apply_async() 一次只能傳遞一個值，它只會放入一個核進行運算，但是傳入值時要注意是可迭代的 (tuple)\n",
    "    res = pool.apply_async(job, (2,)); print(res.get())  # 用get獲得結果\n",
    "    \n",
    "    # Iterate apply_async() results\n",
    "    multi_res = [pool.apply_async(job, (i,)) for i in range(10)]\n",
    "    print([res.get() for res in multi_res])  # 從迭代器中取出\n",
    "    \n",
    "    \n",
    "if __name__ == '__main__':\n",
    "    multicore()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Multiprocessing with Queue\n",
    "\n",
    "`Queue` 的功能是將每個核或線程的運算結果放在隊裡中，等到每個線程或核運行完畢後再從隊列中取出結果，繼續加載運算。原因很簡單，多線程調用的函數不能有返回值，所以使用 `Queue` 存儲多個線程運算的結果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import multiprocessing as mp\n",
    "import time\n",
    "import os"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def washer(dishes, output):\n",
    "    for dish in dishes:\n",
    "        print(f'[{time.time() - tic:.2f}] Washing dish ({os.getpid()}): {dish}')\n",
    "        time.sleep(1)\n",
    "        output.put(dish)\n",
    "        \n",
    "def dryer(input):\n",
    "    name = mp.current_process().name\n",
    "    while True:\n",
    "        dish = input.get()\n",
    "        print(f'[{time.time() - tic:.2f}] {name}({os.getpid()}) - Drying dish: {dish}')\n",
    "        time.sleep(5)\n",
    "        print(f'[{time.time() - tic:.2f}] {name}({os.getpid()}) - Done: {dish}')\n",
    "        input.task_done()  # tell queue that the task is done"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if __name__ == '__main__':\n",
    "    tic = time.time()\n",
    "    dish_queue = mp.JoinableQueue()\n",
    "    \n",
    "    # 逐個加載 Processor 到 dish_queue\n",
    "    dryer_proc1 = mp.Process(target=dryer, args=(dish_queue,))\n",
    "    dryer_proc2 = mp.Process(target=dryer, args=(dish_queue,))\n",
    "    dryer_proc1.daemon = True  # 會在背景一直等著執行 This must be set before start() is called.\n",
    "    dryer_proc2.daemon = True  # 總共 2 dryers\n",
    "    dryer_proc1.start()  # 開始進程 1\n",
    "    dryer_proc2.start()  # 開始進程 2\n",
    "\n",
    "    # 開始工作\n",
    "    dishes = ['apple', 'banana', 'orange', 'salad']\n",
    "    washer(dishes, dish_queue)  # washer 先逐個洗盤子，洗完的盤子丟到 dish_queue\n",
    "    print('All wash work done!!!')\n",
    "    dish_queue.join()  # Block until all items in the queue have been gotten and processed.\n",
    "    print('All Done!')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "[**Queue vs JoinableQueue**](https://stackoverflow.com/a/31230329/3744499)\n",
    "\n",
    "`JoinableQueue` has methods `join()` and `task_done()`, which `Queue` hasn't.\n",
    "\n",
    "> **class multiprocessing.JoinableQueue( [maxsize] )**\n",
    "> \n",
    "> `JoinableQueue`, a `Queue` subclass, is a queue which additionally has `task_done()` and `join()` methods.\n",
    "\n",
    "If you use `JoinableQueue` then you must call `JoinableQueue.task_done()` for each task removed from the queue or else the semaphore used to count the number of unfinished tasks may eventually overflow, raising an exception."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "### Deamon Process"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.03] Starting: daemon\n",
      "[0.03] Starting: non-daemon\n",
      "[0.04] Exiting: non-daemon\n",
      "d.is_alive(): True\n",
      "[5.04] Exiting: daemon\n"
     ]
    }
   ],
   "source": [
    "import multiprocessing\n",
    "import time\n",
    "import sys\n",
    "\n",
    "def daemon():\n",
    "    name = multiprocessing.current_process().name\n",
    "    print(f'[{time.time() - tic:.2f}] Starting: {name}')\n",
    "    time.sleep(5)\n",
    "    print(f'[{time.time() - tic:.2f}] Exiting: {name}')\n",
    "\n",
    "def non_daemon():\n",
    "    name = multiprocessing.current_process().name\n",
    "    print(f'[{time.time() - tic:.2f}] Starting: {name}')\n",
    "    print(f'[{time.time() - tic:.2f}] Exiting: {name}')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    tic = time.time()\n",
    "    \n",
    "    d = multiprocessing.Process(name='daemon',\n",
    "                                target=daemon)\n",
    "    d.daemon = True  # 守護進程就是不阻擋主程序退出，自己幹自己的\n",
    "\n",
    "    n = multiprocessing.Process(name='non-daemon',\n",
    "                                target=non_daemon)\n",
    "    n.daemon = False\n",
    "\n",
    "    d.start()\n",
    "    n.start()\n",
    "\n",
    "    d.join(1)  # 等 d job 完成才繼續，等待n久就不等了\n",
    "    print('d.is_alive():', d.is_alive())\n",
    "    n.join()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
