Question: Just like the question title

for say I have code like

```
func receieveNotification(notification : NSNotification) {

    ......verify notification
    ......retrieve userInfo

}

```

Do I still need to add observer to NSNotificationCenter.defaultCenter() ?
If I do. How to?

Answer: 
You are terminating your program too early. To test this out, add a harmless `time.sleep(10)` in the `cracker` method and observe your program still terminating within a second.

Call [`join`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool.join) to wait for the pool to finish:

```
pool = mp.Pool()
for i in open(sys.argv[2], 'r', errors='ignore'):
    pswd = bytes(i.strip('\n'), 'utf-8')
    pool.apply_async(cracker, (pswd,))

pool.close()  # Indicate that no more data is coming
pool.join()   # Wait for pool to finish processing

runtime =  round(time.time() - startime, 5)
print ("[*] Runtime:", runtime, 'seconds')
sys.exit(0)

```

Additionally, once you find the right password, calling `close` just indicates that no more future tasks are coming - all tasks already submitted will still be done. Instead, call [`terminate`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool.terminate) to kill the pool without processing any more tasks.

Furthermore, depending on the implementation details of `multiprocessing.Pool`, the global variable `pool` may not be available when you need it (and its value isn't serializable anyways). To solve this problem, you can use a callback, as in

```
def cracker(password):
    try:
        zFile.extractall(pwd=password)
    except RuntimeError:
        return
    return password

 def callback(found):
     if found:
         pool.terminate()
 ...
 pool.apply_async(cracker, (pswd,), callback=cb)

```

Of course, since you now look at the result all the time, `apply` is not the right way to go. Instead, you can write your code using [`imap_unordered`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool.imap_unordered):

```
with open(sys.argv[2], 'r', errors='ignore') as passf, \
         multiprocessing.Pool() as pool:
     passwords = (line.strip('\n').encode('utf-8') for line in passf)
     for found in pool.imap_unordered(cracker, passwords):
         if found:
             break

```

Instead of using globals, you may also want to open the zip file (and create a `ZipFile` object) in each process, by using an [`initializer`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool) for the pool. Even better (and way faster), forgo all of the I/O in the first place and read just the bytes you need once and then pass them on to the children.
phihag's answer is the correct solution.

I just wanted to provide an additional detail regarding calling `terminate()` when you've found the correct password. The `pool` variable in `cracker()` was not defined when I ran the code. So trying to invoke it from there simply threw an exception:

```
NameError: name 'pool' is not defined

```

(My `fork()` experience is weak, so I don't completely understand why the global `zFile` is copied to the child processes successfully while `pool` is not. Even if it were copied, it would not be the same `pool` in the parent process, right? So any methods invoked on it would have no effect on the *real* pool in the parent process. Regardless, I prefer [this](https://docs.python.org/3/library/multiprocessing.html#all-start-methods) advice listed within the `multiprocessing` module's *Programming guidelines* section: **Explicitly pass resources to child processes**.)

My suggestion is to make `cracker()` return the password if it is correct, otherwise return `None`. Then pass a callback to `apply_async()` that records the correct password, as well as terminating the pool. Here's my take at modifying your code to do this:

```
#!/usr/bin/env python3

import multiprocessing as mp
import zipfile # Handeling the zipfile
import sys # Command line arguments, and quiting application
import time # To calculate runtime
import os

def usage(program_name):
    print("Usage: {0} <path to zipfile> <dictionary>".format(program_name))
    sys.exit(1)

def cracker(zip_file_path, password):
    print('[*] Starting new cracker (pid={0}, password="{1}")'.format(os.getpid(), password))

    try:
        time.sleep(1) # XXX: to simulate the task taking a bit of time
        with zipfile.ZipFile(zip_file_path) as zFile:
            zFile.extractall(pwd=bytes(password, 'utf-8'))
        return password
    except:
        return None

def main():
    if len(sys.argv) < 3:
        usage(sys.argv[0])

    print('[*] Starting main (pid={0})'.format(os.getpid()))

    zip_file_path = sys.argv[1]
    password_file_path = sys.argv[2]
    startime = time.time()
    actual_password = None

    with mp.Pool() as pool:
        def set_actual_password(password):
            nonlocal actual_password
            if password:
                print('[*] Found password; stopping future tasks')
                pool.terminate()
                actual_password = password

        with open(password_file_path, 'r', errors='ignore') as password_file:
            for pswd in password_file:
                pswd = pswd.strip('\n')
                pool.apply_async(cracker, (zip_file_path, pswd,), callback=set_actual_password)

        pool.close()
        pool.join()

    if actual_password:
        print('[*] Cracked password: "{0}"'.format(actual_password))
    else:
        print('[*] Unable to crack password')
    runtime =  round(time.time() - startime, 5)
    print("[*] Runtime:", runtime, 'seconds')
    sys.exit(0)

if __name__ == "__main__":
    main()

```