途中までですけど・・・
Processの動作のデフォルトは以下ですが、
Windows: spawn
Linux: fork
Linux(Macintosh)では動きWindowsでは動かないということなので上記動作の違いが影響していると思います。以下を実行してみますと
Python
1import dill
2
3def foo():
4 print('foo')
5 bar() # line 7
6
7def bar():
8 print('bar')
9
10if __name__ == '__main__':
11 code = dill.dumps(foo)
12 bar = None
13 o = dill.loads(code)
14 foo()
File "C:/Users/keiji/PyCharmProjects/tkinter_test/spawn_test.py", line 7, in foo
bar()
TypeError: 'NoneType' object is not callable
となることより、dumpsでは指定したオブジェクト(test関数オブジェクト)のみしかシリアライズされていないことがわかります。それは自然な仕様と考えられます。
そうなのだとしたら別プロセスへ環境を復元するには必要なオブジェクトを全てシリアライズ、デシリアライズする必要があると考えなければならないのではないでしょうか?
Windowsでspawnした場合でも実行に必要な関数群自体は新たに起動されたインタープリタへ復元されるので動くような気もしますが、「同一のインタープリタではない」のでid(obj)などオブジェクトの内部情報は元の環境とは異なると思います、spawn/forkで振る舞いに違いがでるのはそういった要因なのかも知れないと思いました。
(ただdillが何を保証しているか何もドキュメントを読んでないので、上記は単なる推測の域を出ません)
追記:書き忘れたことあるので追記しておきます
グローバル変数のスナップショットを取るのがdillの典型的な使い方だと思いますが、質問者さんがわざわざ関数のシリアライズをしているのはdillがどんな動きをするか調べているということなのかなと思いました。
子供プロセスを起動して親プロセスで定義されている関数群を用いるだけならspawnモード/forkモードにかかわらずProcessで起動した子供プロセスでそのまま親プロセスのモジュールの状態が再現された状態で動きます(※1)。つまり特殊なことをしようとしているのでない限りdillにより関数のシリアライズ/デシリアライズの必要はないと思います。
実際、Processを用いても下記のようにfoo/bar/baz全て定義された状態で子プロセスが動きます。(質問者さんもご存知だとは思います)
Python
1from multiprocessing import Process
2
3def foo():
4 print('foo')
5 bar()
6
7def bar():
8 print('bar')
9 baz()
10
11def baz():
12 print('baz')
13
14if __name__ == '__main__':
15 p = Process(target=foo, args=())
16 p.start()
17 print('waiting for child')
18 p.join()
19 print('child has been finished')
※1: forkシステムコールがないWindowsでは親プロセスとほぼ同一の環境で子供プロセスが開始するLinuxとは若干ことなるセマンティクスになります。Processのリファレンスにある通りです。