O primeiro passo (como Péter diz) é o log. Embora, na minha experiência, isso seja frequentemente problemático. No processamento paralelo pesado, isso geralmente não é possível. Eu tive que depurar algo semelhante com uma rede neural uma vez, que processou 100k de nós por segundo. O erro ocorreu apenas depois de várias horas e até uma única linha de saída diminuiu tanto as coisas que levaria dias. Se o registro for possível, concentre-se menos nos dados, mas mais no fluxo do programa, até que você saiba em qual parte isso acontece. Apenas uma linha simples no início de cada função e, se você encontrar a função correta, divida-a em pedaços menores.
Outra opção é remover partes do código e dados para localizar o bug. Talvez até escreva um pequeno programa que leve apenas algumas das classes e execute apenas os testes mais básicos (ainda em vários segmentos, é claro). Remova tudo que estiver relacionado à GUI, por exemplo, qualquer saída sobre o estado de processamento real. (Eu achei a interface do usuário a fonte do bug com bastante frequência)
No seu código, tente seguir o fluxo lógico completo de controle entre inicializar o bloqueio e liberá-lo. Um erro comum pode ser bloquear no início de uma função, desbloquear no final, mas ter uma declaração de retorno condicional em algum lugar no meio. Exceções também podem impedir a liberação.