Python - Multithreading - Does Lock have to be global?

No, it does not have to be global. You can create it in a function and then pass it to your threads as an argument like so:

i = 0

def do_work(lock):
    global i

    while i < len(my_list):
        with lock: # cleaner way to .acquire() and .release()
            my_i = i
            i += 1

def main():
    lock = threading.RLock()

    workers = [threading.Thread(target=do_work, args=lock,) for _ in range(8)]
    for worker in workers: