cgroups are not jobs (in the Windows NT and VMS senses), and lack the functionality of a true job mechanism. They do not magically fix service management on Linux.
You should read the systemd people's gripes on the subject over the years. They were rather expecting (version 2) cgroups to be a true job mechanism, and they've complained every time that it has once again turned out not to be. There are systemd bugs, outstanding even now, dealing with the inability to atomically kill everything in a control group.
An operating system kernel that provides a "job" abstraction provides a way of cancelling/killing an entire "job".
Witness the Win32 TerminateJobObject()
mechanism, for example.
When systemd terminates all of the processes in a cgroup, it doesn't issue a single "terminate job" system call. There isn't such a thing. It instead sits in a loop in application-mode code repeatedly scanning all of the process IDs in the cgroup (by re-reading a file full of PID numbers) and sending signals to new processes that it hasn't seen before. There are several problems with this.
systemd can be slower than whatever is reaping child processes within the process group, leading to the termination signals being sent to completely the wrong process: one that just happened to re-use the same process ID in between systemd reading the cgroup's process list file and it actually getting around to sending signals to the list of processes. The order of events is:
systemd reads PID N from the cgroup's process ID list, then its timeslice happens to expire;
process N is scheduled and exits;
process N's reaper (its parent, or a sub-reaper) cleans up the zombie in the process table;
some vital part of the system is scheduled and spawns a new process re-using ID N;
systemd is finally scheduled, works down the list of PIDs that it read before, and merrily kills the new process.
A program that forks new processes quickly enough within the cgroup can keep systemd spinning for a long time, in theory indefinitely as long as suitable "weather" prevails, as at each loop iteration there will be one more process to kill. Note that this does not have to be a fork bomb. It only has to fork enough that systemd sees at least one more new process ID in the cgroup every time that it runs its loop.
systemd keeps process IDs of processes that it has already signalled in a set, to know which ones it won't try to send signals to again. It's possible that a process with ID N could be signalled, terminate, and be cleaned out of the process table by a reaper/parent; and then something within the cgroup fork a new process that is allocated the same process ID N once again. systemd will re-read the cgroup's process ID list, think that it has already signalled the new process, and not signal it at all.
These are addressed by a true "job" mechanism. But cgroups are not such. cgroups were intended as an improvement upon the traditional Unix resource limit mechanisms, addressing some of their long-standing and well-known design flaws. They weren't designed to be the equivalent of a VMS or a Windows NT Job Object.
No, the freezer is not the answer. Not only does systemd not use the freezer, but the systemd people explicitly describe it as having "broken inheritance semantics of the kernel logic". You'll have to ask them what they mean by that, but the freezer does not, for them, magically turn cgroups into a job mechanism either.
Moreover: This is not to mention that Docker and others will manipulate the freeze status of control groups for their own purposes, and there is no real race-free mechanism for sharing this setting amongst multiple owners, such as an atomic read-and-update for it.