ZetCode

Fortran coarrays

last modified November 14, 2026

A coarray is an array whose extra dimensions are spread over several images, that is, several running copies of the same program, each with its own memory. The images exchange data by naming the copy they want, as in x[2], and synchronise with sync all. Unlike OpenMP, where the threads share one address space, coarrays are a model of distributed memory that works on one processor and on a cluster alike, and the language itself, rather than a library, provides the communication.

This article declares coarrays, reads the number of images and the number of the current one, accesses another image's copy, synchronises the images, collects values with co_sum, co_max, co_min and co_reduce, guards a critical section with a construct, splits a loop over the images and computes pi from the terms each image summed. The programs are compiled with -fcoarray=single, which runs the whole thing as one image; building for several images is explained, and shown to need a library that is not installed here.

Images

A coarray is declared with square brackets after the usual bounds: integer :: value[*] gives every image one scalar named value. The bracket holds the codimensions, and an asterisk means that the extent follows the number of images, so the array has one element on each of them. num_images() returns how many images are running and this_image() the number of the current one, counting from one.

co1.f90
program co1
    implicit none
    integer :: value[*]
    integer :: i, total

    print '(a, i0)', 'images: ', num_images()
    print '(a, i0)', 'this image: ', this_image()

    value = this_image() * 10
    sync all
    print '(a, i0, a, i0)', 'image ', this_image(), ' holds ', value

    total = 0
    do i = 1, num_images()
        total = total + value[i]
    end do
    print '(a, i0)', 'the total over all images: ', total
end program co1

Each image writes its own value and, after the barrier, reads the copies of all images by putting the image number in brackets, as in value[i]. With one image the loop has a single pass and the total is 10. With four images the four values would be 10, 20, 30 and 40, the loop would read all four, and the total would be 100; the source does not change, only the number of images does.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co1.f90 -o co1
$ ./co1
images: 1
this image: 1
image 1 holds 10
the total over all images: 10

A coarray with ordinary dimensions

The two kinds of dimension mix freely. real :: row(4)[*] is a four-element array on every image, and the codimension adds the image axis behind it. lcobound and ucobound report the bounds of the codimensions, and with the asterisk form they are one and the number of images. Reading row(2)[1] takes the second element from image one.

co3.f90
program co3
    implicit none
    real :: row(4)[*]
    integer :: i

    row = real(this_image())
    row(2) = 10.0 * this_image()
    sync all

    print '(a, i0)', 'this image: ', this_image()
    print '(a, 4f6.1)', 'its own row: ', row
    print '(a, f6.1)', 'row(2) on image 1: ', row(2)[1]
    print '(a, i0)', 'lower codimension: ', lcobound(row)
    print '(a, i0)', 'upper codimension: ', ucobound(row)
    print '(a, i0)', 'size in coarray dimensions: ', ucobound(row) - &
        lcobound(row) + 1
    do i = 1, num_images()
        print '(a, i0, a, f6.1)', 'image ', i, ' holds ', row(2)[i]
    end do
end program co3

The array bounds and the codimension bounds are separate, which the output shows by printing a four-element row and a codimension of extent one. The loop at the end reads the same element from every image with a variable index in the brackets, the general form of a remote access. On four images every line of that loop would print the same number here, because all images set the element the same way from their own number only in the first assignment; the second assignment gives each copy a different value.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co3.f90 -o co3
$ ./co3
this image: 1
its own row:    1.0  10.0   1.0   1.0
row(2) on image 1:   10.0
lower codimension: 1
upper codimension: 1
size in coarray dimensions: 1
image 1 holds   10.0

Synchronisation and critical sections

An assignment to a remote image is not visible to that image until a synchronisation: sync all waits for every image to arrive and makes all writes before it visible to all reads after it. Without a barrier a program may read a stale value, and the result depends on timing. For a section that only one image at a time may execute, Fortran has the critical construct, which is a lock shared by all images.

co4.f90
program co4
    implicit none
    integer :: counter[*]
    integer :: i

    counter = 0
    sync all

    do i = 1, num_images()
        if (i /= this_image()) counter[i] = counter[i] + 1
    end do
    sync all

    critical
        print '(a, i0, a, i0)', 'image ', this_image(), &
            ' sees a counter of ', counter
    end critical
end program co4

The program increments the copies that belong to the other images, so each image ends with the number of images minus one, and prints its own copy inside a critical section so that the lines do not interleave. With one image no other copy exists, the loop is empty, and the counter stays at zero. The two barriers have different jobs: the first makes sure every copy starts at zero before anyone adds to it, the second makes the additions visible before they are read.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co4.f90 -o co4
$ ./co4
image 1 sees a counter of 0

Collectives

A collective subroutine combines one value from every image and leaves the result on every image. co_sum adds, co_max and co_min reduce with the obvious operators, and co_reduce takes a pure function of two arguments and folds the values with it. All of them synchronise the images, so an explicit sync all before the call is unnecessary.

co2.f90
program co2
    implicit none
    integer :: x, y
    real :: r
    logical :: ok

    x = this_image() * 7
    y = this_image() * 3
    r = real(this_image())
    ok = this_image() == 2
    call co_sum(x)
    call co_max(y)
    call co_min(r)
    call co_reduce(ok, both)
    print '(a, i0)', 'sum: ', x
    print '(a, i0)', 'max: ', y
    print '(a, f0.1)', 'min: ', r
    print '(a, l1)', 'some image was 2: ', ok
contains
    pure logical function both(a, b) result(c)
        logical, intent(in) :: a, b
        c = a .or. b
    end function both
end program co2

With one image each collective returns its argument unchanged: the sum of 7 is 7, the maximum of 3 is 3, the minimum of 1.0 is 1.0, and the reduction of .false. is false. On four images the same program would print 70, 12, 1.0 and true, because the values are 7, 14, 21 and 28 for the sum, 3, 6, 9 and 12 for the maximum, and image two would make the logical reduction true. The function given to co_reduce must be pure and associative, so that the order in which the images are folded does not matter.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co2.f90 -o co2
$ ./co2
sum: 7
max: 3
min: 1.0
some image was 2: F

Splitting the work

The usual shape of a coarray program is a loop over the local part of a global range, followed by a collective. Each image computes the first and last index of its share from this_image() and num_images(), so the same source divides the work differently as the number of images changes. The program below prints the share it would take and the totals it implies.

co6.f90
program co6
    implicit none
    integer, parameter :: n = 1000
    integer :: chunk, first, last

    chunk = (n + num_images() - 1) / num_images()
    first = (this_image() - 1) * chunk + 1
    last = min(first + chunk - 1, n)

    print '(a, i0, a, i0)', 'images in the run: ', num_images(), &
        ', this one: ', this_image()
    print '(a, i0, a, i0)', 'this image would process ', first, ' to ', last
    print '(a, i0)', 'iterations assigned here: ', last - first + 1
    print '(a, i0)', 'iterations in total: ', &
        (last - first + 1) * num_images()
    print '(a, i0)', 'the static chunk size: ', chunk
end program co6

With one image the share is the whole range of a thousand iterations. With four images each would take two hundred and fifty, and with three the chunk size rounds up to 334, so the last image would take only 332 and the min would matter. The expression (n + images - 1) / images is the integer ceiling of the division, the standard way to make the chunks cover the range without a gap.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co6.f90 -o co6
$ ./co6
images in the run: 1, this one: 1
this image would process 1 to 1000
iterations assigned here: 1000
iterations in total: 1000
the static chunk size: 1000

A calculation split over the images

The program below computes pi from the Leibniz series, whose terms are 4 * (-1)**(k+1) / (2k-1). Each image sums its own range into a coarray scalar, co_sum adds the partial sums, and every image then holds the same total and prints the same estimate. The series converges slowly, so the result is a demonstration of the mechanics rather than a good way to compute pi.

co5.f90
program co5
    use iso_fortran_env, only: real64
    implicit none
    integer, parameter :: n = 10000000
    integer :: k, chunk, first, last
    real(real64) :: partial[*], term, pi

    chunk = n / num_images()
    first = (this_image() - 1) * chunk + 1
    last = first + chunk - 1

    partial = 0.0_real64
    do k = first, last
        term = 1.0_real64 / real(2 * k - 1, real64)
        if (mod(k, 2) == 0) term = -term
        partial = partial + term
    end do

    sync all
    call co_sum(partial)

    pi = 4.0_real64 * partial
    print '(a, i0)', 'images: ', num_images()
    print '(a, i0, a, i0)', 'this image summed terms ', first, ' to ', last
    print '(a, f0.8)', 'pi from the series: ', pi
    print '(a, es10.2)', 'the error: ', abs(pi - 4.0_real64 * atan(1.0_real64))
end program co5

Ten million terms give 3.14159255, an error of one in the seventh decimal, which is what the alternating series leaves after that many terms. The line that prints the range shows the share of this image, and with four images four different ranges would be summed and combined, with the same final answer to the last digit that the floating-point addition allows. The sync all before the collective is redundant but harmless, and it is kept to mark the end of the local phase.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co5.f90 -o co5
$ ./co5
images: 1
this image summed terms 1 to 10000000
pi from the series: 3.14159255
the error:   1.00E-07

Building for more than one image

-fcoarray=single is the easy mode: the compiler implements the coarray syntax in one process, which is enough to develop and test the logic but never runs two images. For real parallelism gfortran needs -fcoarray=lib and the OpenCoarrays runtime, which supplies the communication functions and normally starts the images with MPI.

co3.f90
program co3
    implicit none
    real :: row(4)[*]
    integer :: i

    row = real(this_image())
    row(2) = 10.0 * this_image()
    sync all

    print '(a, i0)', 'this image: ', this_image()
    print '(a, 4f6.1)', 'its own row: ', row
    print '(a, f6.1)', 'row(2) on image 1: ', row(2)[1]
    print '(a, i0)', 'lower codimension: ', lcobound(row)
    print '(a, i0)', 'upper codimension: ', ucobound(row)
    print '(a, i0)', 'size in coarray dimensions: ', ucobound(row) - &
        lcobound(row) + 1
    do i = 1, num_images()
        print '(a, i0, a, f6.1)', 'image ', i, ' holds ', row(2)[i]
    end do
end program co3

Compiling the same program with -fcoarray=lib gets as far as the linker, which reports every runtime function that the library should have provided. The names are the application binary interface between the compiled code and the runtime, and the missing one at the top is the registration call that every coarray makes at start-up. Installing OpenCoarrays, or a compiler that bundles a coarray runtime, is the step that turns these programs into multi-image ones; nothing in the source changes.

$ gfortran -std=f2018 -fcoarray=lib -c co3.f90 -o co3.o
$ gfortran co3.o -o co3lib
/usr/bin/ld: co3.o: in function `_caf_init.1.0':
co3.f90:(.text+0x64): undefined reference to `_gfortran_caf_register'
/usr/bin/ld: co3.o: in function `MAIN__':
co3.f90:(.text+0x95): undefined reference to `_gfortran_caf_this_image'
/usr/bin/ld: co3.f90:(.text+0xc8): undefined reference to `_gfortran_caf_this_image'
/usr/bin/ld: co3.f90:(.text+0xfc): undefined reference to `_gfortran_caf_sync_all'
/usr/bin/ld: co3.f90:(.text+0x16d): undefined reference to `_gfortran_caf_this_image'
/usr/bin/ld: co3.f90:(.text+0x3e1): undefined reference to `_gfortran_caf_get'
/usr/bin/ld: co3.f90:(.text+0x5eb): undefined reference to `_gfortran_caf_num_images'
/usr/bin/ld: co3.f90:(.text+0x74e): undefined reference to `_gfortran_caf_num_images'
/usr/bin/ld: co3.f90:(.text+0x79f): undefined reference to `_gfortran_caf_num_images'
/usr/bin/ld: co3.f90:(.text+0x927): undefined reference to `_gfortran_caf_get'
/usr/bin/ld: co3.o: in function `main':
co3.f90:(.text+0x99a): undefined reference to `_gfortran_caf_init'
/usr/bin/ld: co3.f90:(.text+0x9c4): undefined reference to `_gfortran_caf_finalize'
collect2: error: ld returned 1 exit status

Next steps

The OpenMP article reaches the same goal through threads in one address space, and the parallel loops article covers do concurrent, which a coarray program can also use inside each image. The arrays article explains the dimensions that a codimension extends, the modules article explains the use of a standard module such as iso_fortran_env, and the error handling article explains the status of a failed link.

Source

Fortran 2018 standard (J3/18-007r1)

GNU Fortran manual: Coarray Programming

OpenCoarrays

In this article we have declared coarrays with and without ordinary dimensions, read the image number and the number of images, accessed a remote copy with an index in brackets, synchronised with sync all and protected a section with critical, combined values with co_sum, co_max, co_min and co_reduce, divided a loop over the images, computed pi from the terms each image summed, and seen that a multi-image build needs a runtime library that this machine does not have. The previous article, OpenMP with gfortran, shares the work within one process instead.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.