running multiple loops simultaneosly
If you want to do things with a loop, then you need to change things around so the loop repeat is governed by the repeating synchronous mains cycle. This is not the cleanest way to do things, but it should be possible to get it to work.

Note there is only one slow thing to control, the phase-shift speed control of the fan. It can accomodate many other 'fast' things, read a switch, set a relay, within the time it takes to wait for the next zero crossing.

while running:
    {
    wait_for_zero_crossing();
    read_fan_speed_control();
    compute_speed_to_delay_for_fan();
    delay_for_fan_speed_control_time();
    pulse_fan_triac();

    set_fan_relay();    
    read_light_control();
    set_light_relay();    
    ...    
    read_other_control();
    set_other_relay();
    }


A rather better method is to use interrupts. Where you have a synchronous task like waiting for mains cycles, and an asynchronous task like reading switches, it is good to use interrupts to handle the timing critical part of the job, like this. Generally you want the interrupt service routine (ISR) to execute as quickly as possible, ie do as little work as possible. As long as a few cycles are spent outside the ISR, then your asynchronous code will run.

volatile int fan_control;      # needed so main and ISR communicate correctly

setup_ISR_to_run_every_100uS;  # for instance, 10kHz

while running:
    {
    read_fan_speed_control();
    fan_control = compute_speed_to_delay_for_fan();
    read_light_control();
    set_light_relay();
    ...
    read_other_control();
    set_other_relay();
    }

ISR:
    {
    if zero_crossing_just_happened():
        count = 0;
    else:
        count += 1;
    if count == fan_control:
        pulse_fan_triac();
    ...
    if count == other_control:
        pulse_other_triac();
    }


As you can see, it's easy to expand this ISR section to accommodate other phase shift control channels easily. I'll leave how to implement zero_crossing_just_happened() as an exercise for the reader. 

These fragments are sketches, obviously much has been omitted. I've shown the volatile declaration as this is often forgotten in these situations. It forces the compiler to re-read the value from memory on every read, and not to try to optimise by using a register-based copy.

Once you've learnt to use interrupts, you can use them for things like scanning a multiplexed device, grabbing data from ADCs in the background, all sorts of things you can't do nicely with a single asynchronous loop.