Search code examples
concurrencyeiffelagents

Scoop with agents


I'm trying to use an agent callback concurrently. Unfortunately, no matter what I do it always seems to run sequentially instead of parallel. (without the agent it doesn't)

main class(APPLICATION):

class
    APPLICATION

inherit
    ARGUMENTS

create
    make

feature {NONE} -- Initialization

    make
            -- Run application.
        local
            a1 : separate PROCEDURE
            a2 : separate PROCEDURE
        do
            create my_counter.make (1, 100_000_000)
            create my_counter2.make (2, 100_000_000)

            launch_counter(my_counter)
            launch_counter(my_counter2)

        end

feature -- Launch

    launch_counter(c: separate COUNTER)
        do
            c.run (5, agent print_hello)
        end

    print_hello
        do
            print("Hello!%N")
        end

feature -- Access

    my_counter : separate COUNTER
    my_counter2 : separate COUNTER

end

counter class:

class
    COUNTER

inherit
    EXECUTION_ENVIRONMENT


create
    make

feature -- Initilzation
    make (i: INTEGER; delay: INTEGER)
        do
            id := i
            delay_time := delay
        end

feature -- Stuff

    run (times: INTEGER; p: separate PROCEDURE)
    local
        c : INTEGER
    do
        from
            c := times
        until
            c = 0
        loop
            print("COUNTER: " + id.out)
            p.call
            sleep(delay_time)
            c := c - 1

        end

    end

feature {NONE} -- Access

    delay_time : INTEGER
    id: INTEGER

end

expected output:

COUNTER: 1Hello!
COUNTER: 2Hello!
COUNTER: 1Hello!
etc.

actual output:

COUNTER: 1Hello!
COUNTER: 1Hello!
COUNTER: 1Hello!
COUNTER: 1Hello!
COUNTER: 1Hello!
COUNTER: 2Hello!
COUNTER: 2Hello!
COUNTER: 2Hello!
COUNTER: 2Hello!
COUNTER: 2Hello!

What would I have to change to make this run as expected?


Solution

  • The agent object keeps a reference to the target, in your example to the root object of type APPLICAITON, therefore all calls to print_hello get synchronized. To avoid that, the objects on which the agent is called should be recorded in a COUNTER object and used from there.

    This can be achieved by adding an attribute action to the class COUNTER and updating its creation procedure

    make (i: INTEGER; delay: INTEGER; a: separate PROCEDURE)
        do
            id := i
            delay_time := delay
            action := a
        end
    ...
    action: separate PROCEDURE
    

    Then instead of p.call in COUNTER.run the following code will be used (the feature run has no argument p anymore):

            separate action as p do
                p.call
            end
    

    Now p is not locked on entry to the feature run and therefore callbacks can be executed alternately by different processors.