Извините за прямоту, но проблема в недостаточном знании Вами матчасти.
Что почитать по теме:
Zombie process, а также
Docker and the PID 1 zombie reaping problem.
А теперь ближе к Вашему примеру.
Cуть: и SITERM'ом, и SIGKILL'ом Вы child процесс успешно убиваете, после чего он превращается в зомби-процесс, то есть все еще висит в памяти под своим PID ожидая reaping (системного вызова wait() от parent процесса). После завершения parent процесса наш зомби-child усыновляется init процессом, который его и reap'ит.
Если запустить Ваши программы в том виде, котором Вы их запостили, и во время тиканья написать ps, то увидим следующую картину:
459 ttys000 0:00.03 -bash
498 ttys000 0:00.00 ./parent
499 ttys000 0:00.00 (child)
То, что child указан в скобках, как раз и означает, что процесс был остановлен (то есть он совершил системный вызов exit()) и ожидает reaping'а (когда результат exit()'а прочтут wait()'ом).
Если перед посылкой child'у добавить
time.Sleep(10 * time.Second)
и сразу после вывода child'овского PID'а набрать глянуть что творится в процессах, то увидим такую картину:
459 ttys000 0:00.04 -bash
510 ttys000 0:00.00 ./parent
511 ttys000 0:00.00 ./child
Сравните с тем как он отображается в процессах после того, как был убит.
А если мы после посылки сигнала child'у добавим
cmd.Process.Wait()
, то Ваш цикл ticker'а все равно, возможно, еще будет крутится, если в системе появился новый процесс с таким PID'ом, но если при этом заглянуть в ps, то увидим, что нашего child'а уже нет совсем:
459 ttys000 0:00.05 -bash
536 ttys000 0:00.01 ./parent
Кстати, о волшебной силе Process.Wait() пишется прямо в
его доке.