Hi, and welcome to the Julia community!
The issue is that permutations
returns a generator, i.e. a way to (sequentially) produce permutations without actually computing them yet. Using collect
does perform this computation, for all entries at once.
The easiest fix would be to use perm = nthperm(1:n+1, i)
inside of the @threads for
loop.
I expect this to work mostly fine in the case of permutations (though I’m not necessarily knowledgeable on how to efficiently list them). But in certain cases it might be more efficient to iterate, rather than get each element individually. For example, obtaining the first 10 Fibonacci numbers jointly is more efficient than finding them individually (i.e. without caching/memoisation, which is what the iteration state does). When direct indexing is not possible, you would need to manually perform the iteration in the main thread, creating new tasks when previous ones have finished.
Manual task creation example
In the code below we manually iterate
over the permutations. When we have resources available, we create a Task
to handle the current permutation. To know when a task is done (without constantly looping over them), we use a Channel
which will contain the indices of Task
s which have finished.
using Combinatorics
function handle_permutation(perm, channel, task_idx)
sleep_time = rand() * 5 # Sleep for a random time up to 5 s, to simulate heterogenous computation times
sleep(sleep_time)
println("$task_idx: $perm ($sleep_time)")
put!(channel, task_idx) # Signal our task is done
end
function threaded_handle_permutations(perms, nb_tasks)
channel = Channel{Int}(nb_tasks)
tasks = Array{Task}(undef, nb_tasks)
it = iterate(perms) # Either a permutation and state, or nothing when we are finished
for task_idx = 1:nb_tasks # Setup, where we don't have to wait until Tasks are done
isnothing(it) && break
perm, state = it
let perm = perm
tasks[task_idx] = Task(() -> handle_permutation(perm, channel, task_idx))
end
schedule(tasks[task_idx]) # (or use @spawn)
it = iterate(perms, state)
end
while !isnothing(it)
perm, state = it
task_idx = take!(channel) # tasks[task_idx] is ready
let perm = perm
tasks[task_idx] = Task(() -> handle_permutation(perm, channel, task_idx))
end
schedule(tasks[task_idx])
it = iterate(perms, state)
end
wait.(tasks) # Wait until all Tasks have finished
close(channel)
end
threaded_handle_permutations(permutations(1:4), Threads.nthreads())
with example output
4: [1, 3, 4, 2] (1.2117935089662901)
2: [1, 2, 4, 3] (1.5704595207841299)
5: [1, 4, 2, 3] (2.7562927397700028)
7: [2, 1, 3, 4] (3.427912051023216)
1: [1, 2, 3, 4] (3.9286201986106724)
3: [1, 3, 2, 4] (4.150059037617181)
8: [2, 1, 4, 3] (4.41249434461974)
1: [3, 1, 2, 4] (0.8946810612178713)
6: [1, 4, 3, 2] (4.943705002447017)
8: [3, 2, 1, 4] (0.5524897265787826)
4: [2, 3, 1, 4] (4.140999678913283)
2: [2, 3, 4, 1] (4.170628020371655)
1: [3, 2, 4, 1] (0.9551984725843093)
7: [2, 4, 3, 1] (2.4064765159373414)
6: [3, 4, 1, 2] (1.0672378759488854)
5: [2, 4, 1, 3] (3.5553860548631944)
5: [4, 3, 2, 1] (0.5065772298310989)
3: [3, 1, 4, 2] (3.9094692535870177)
6: [4, 3, 1, 2] (2.789216187966278)
2: [4, 1, 3, 2] (3.504362858170555)
8: [3, 4, 2, 1] (4.593180548236061)
7: [4, 2, 3, 1] (3.935449181248672)
4: [4, 1, 2, 3] (4.61345171679344)
1: [4, 2, 1, 3] (4.4173984501241454)