Coarray Teams, Events, Collectives and Atomics
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-19. It continues
Coarrays, Images and Synchronization, which
covers images, codimensions, coindexed access, segments and sync all, sync images, sync memory, lock and critical.
Here are the features built on that base: teams (the subsets of images, 11.1.5 and 11.7.6 to 11.7.9), events
(one-sided signalling, 11.7.7 and 11.7.8), the Fortran 2023 put with notify (11.6 and 9.6), the collective
subroutines (16.6 and 16.9.54 to 16.9.58), the atomic subroutines (16.5 and 16.9.28 to 16.9.38) and the support for
failed and stopped images. Teams, events, collectives, atomics and failed-image support entered the language in Fortran 2018
from the technical specification ISO/IEC TS 18508:2015 (Annex C.1 of J3/23-007r1). Put with notify and the permission for
an array or allocatable object to have a coarray component are Fortran 2023 (Introduction of J3/23-007r1). The
running example is still heatflow, the 2D heat-diffusion program: each image owns a slab of the plate, as in
heatflow/parallel/coarray_halo.f90, and the examples here are the coordination around that slab: global reductions,
a split of the plate into two teams, a hand-off between two images and a ticket counter.
What was compiled, with what, and what works
The multi-image features are the part of Fortran that compilers support least, so this page is explicit about it. Every
example was compiled with GNU Fortran 16.2 as
gfortran -fcoarray=lib -std=f2023 -Wall -Wextra -pedantic -fcheck=all file.f90 -lcaf_shmem (the library built with GCC 16,
shared-memory images in separate processes; the number of images is set by the environment variable GFORTRAN_NUM_IMAGES,
here 4) with no warning and run for real, except where an example is labelled as rejected. The same sources were also tried
with OpenCoarrays 2.10.3 (caf and cafrun, built on MPICH) and with LLVM Flang 23. The result, per feature:
| Feature | gfortran 16.2, -fcoarray=lib with libcaf_shmem |
OpenCoarrays 2.10.3 | LLVM Flang 23 |
|---|---|---|---|
|
works (with |
works |
|
|
works, collectives inside |
the link fails: undefined reference to |
"not yet implemented" or link errors |
Image selectors |
works |
not tested (the teams do not link) |
not available |
Image selector |
rejected at run time: "requested team not found" |
not tested |
not available |
|
works |
works |
"not yet implemented: EVENT POST runtime" |
|
works |
works, but the ticket check of the atomics example printed |
"not yet implemented: intrinsic atomic_define" |
|
works, with caveats (see Failed and stopped images) |
|
not available |
Put with notify ( |
rejected at compile time |
not tested |
not available |
Array or allocatable object with a coarray component (Fortran 2023) |
rejected at compile time |
not tested |
not available |
|
rejected at compile time |
not tested |
not available |
Flang says, by default, "Multi-image features are experimental and are disabled by default, use '-fcoarray' to enable".
With -fcoarray it prints a warning that multi-image support "is still experimental and in development" and then stops
at the first construct it cannot lower. The diagnostics are quoted where each feature is discussed.
Without a -fcoarray option gfortran refuses coarray syntax (Fatal Error: Coarrays disabled at (1), use '-fcoarray=' to enable), and with
-fcoarray=single it accepts the syntax but there is only one image: the event handshake below stops at its own
error stop 'needs two images'. Multi-image runs need -fcoarray=lib and a library, here libcaf_shmem.
Collective subroutines
A collective subroutine (16.6) computes one value from the contributions of all the images of the current team, without the program having to synchronize them by hand. Every image of the team calls it, in unordered segments (a collective is not an image control statement), and the calls correspond: the same collective, in the same order, with arguments of the same type, kind and shape. The five standard collectives are:
| Subroutine | Effect | Clause |
|---|---|---|
|
|
16.9.58 |
|
maximum and minimum; integer, real or character |
16.9.55, 16.9.56 |
|
every image receives the value of |
16.9.54 |
|
generalized reduction with a |
16.9.57 |
The common rules:
-
aisintent(inout), is not coindexed, and has the same shape, type and type parameters on every image. It may be an array, in which case the reduction is element by element. -
Without
result_imageevery image gets the result. Withresult_image=konly imagekgets it andabecomes undefined on the others, because the processor may use it for intermediate values. This is a common source of stale reads: aftercall co_min(t, result_image=1)readton image 1 only. -
The
operationargument ofco_reduceis apurefunction with exactly two scalar, non-optional, non-allocatable arguments and a result, all of the type and type parameters ofa; it shall be the same function on every image. The standard says it shall implement a mathematically associative operation and that the order of evaluation is otherwise not specified, so a non-commutative operation can give an order-dependent result (note in 16.9.57).co_reducereplaced the nameoperatorbyoperationin Fortran 2018 and no longer requires commutativity (Annex C.1). -
With
statpresent, an error is reported instead of terminating:statis zero on success,stat_stopped_imageif an image of the team has stopped, otherwisestat_failed_imageif one has failed, otherwise another positive, processor-dependent value;errmsgreceives an explanatory message. Withoutstat, an error initiates error termination (16.6). The standard also allows a collective to succeed on one image while it fails on another (16.6), sostatmust be tested on every image. -
A collective inside a
change teamconstruct acts on the current team, which is what makes teams useful (see the next section). -
Floating-point sums are approximations: the order of the additions is not specified, so the last digits can change from run to run and with the number of images.
The program below does the collective work of one heatflow time step: the hottest and coldest temperatures, the total heat, the value of largest magnitude by a user-defined reduction, and the configuration read on image 1 and broadcast to the others. The reduction operation is in a module, which keeps the example free of the nested-procedure trampoline that an internal procedure passed as an actual argument can require.
module slab_ops
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, max_abs
integer, parameter :: dp = real64
contains
! the operation handed to co_reduce: pure, two scalar arguments, same type as the result
pure function max_abs(a, b) result(c)
real(dp), intent(in) :: a, b
real(dp) :: c
c = merge(a, b, abs(a) >= abs(b))
end function max_abs
end module slab_ops
program collectives
use slab_ops, only: dp, max_abs
implicit none
integer :: me, np, stat
real(dp) :: t_max, t_min, heat, worst, dt
real(dp) :: cfg(3)
character(len=64) :: msg
me = this_image()
np = num_images()
! pretend each image owns a slab of the plate with these extreme temperatures
t_max = 10.0_dp*me
t_min = -1.5_dp*me
heat = 0.25_dp*me
call co_max(t_max) ! the result is on every image
call co_min(t_min, result_image=1) ! only image 1 holds the result afterwards
call co_sum(heat) ! total heat, on every image
worst = (-1.0_dp)**me*me ! +/- sign pattern: -1, 2, -3, 4
call co_reduce(worst, max_abs) ! the value of largest magnitude: 4.0
cfg = 0.0_dp
if (me == 1) cfg = [1.0e-4_dp, 0.5_dp, 2.0_dp] ! only image 1 read the input
call co_broadcast(cfg, source_image=1)
dt = cfg(1)
stat = -1
msg = ''
call co_sum(dt, stat=stat, errmsg=msg) ! with stat= an error is reported instead of terminating
if (me == 1) then
print '(a,f7.2)', 'co_max : ', t_max
print '(a,f7.2)', 'co_min (image 1) : ', t_min
print '(a,f7.2)', 'co_sum : ', heat
print '(a,f7.2)', 'co_reduce max_abs : ', worst
print '(a,3f8.4)', 'co_broadcast : ', cfg
print '(a,es10.2,a,i0)', 'sum of dt : ', dt, ' stat=', stat
end if
sync all
if (me == np) print '(a,3f8.4)', 'last image also has: ', cfg
end program collectives
Output (4 images):
co_max : 40.00
co_min (image 1) : -6.00
co_sum : 2.50
co_reduce max_abs : 4.00
co_broadcast : 0.0001 0.5000 2.0000
sum of dt : 4.00E-04 stat=0
last image also has: 0.0001 0.5000 2.0000
The same program, built with caf and run with cafrun -n 4, printed the same lines. Flang 23 stops on co_reduce: with -fcoarray
it reports not yet implemented: coarray: intrinsic co_reduce, and without it the first coarray-related statement is refused as above.
Teams
A team is a subset of the images of the program that acts, for a stretch of the execution, as if it were the whole program (Annex C.1 describes it as letting a subset of the images act together as if it were the set of all images). The
images at the start form the initial team. A team formed from the current team by form team is a child of it; the current team is
its parent; teams with the same parent are siblings. Each team has a team number: positive for a child team, and -1 for the
initial team (3.142.5 and 16.9.207). Inside a team, the images are numbered 1 to the number of images in the team.
images 1 2 3 4"] -->|"form team (1, half)"| T1["team 1: images 1 2
this_image() = 1 2"] I -->|"form team (2, half)"| T2["team 2: images 3 4
this_image() = 1 2"] T1 -->|"end team"| I T2 -->|"end team"| I
The pieces:
| Construct or procedure | What it does | Clause |
|---|---|---|
|
opaque type in |
16.10.2.34 |
|
collective over the current team: every image gives the number of the team it joins; a new team is created for each distinct number; the variable identifies it. |
11.7.9 |
|
the construct that makes the team the current team. It synchronizes all images of the new team on entry and again on |
11.1.5 |
|
synchronizes the images of the team named: the current team, an ancestor, or a child of the current team |
11.7.6 |
|
returns the team value for |
16.9.95 |
|
the team number of the team (the current team if absent) |
16.9.207 |
|
the same inquiries, relative to a given team |
16.9.208, 16.9.156, 16.9.107 |
|
16.9.87, 16.9.199, 16.9.108 |
form team and the change team statement must be executed by all active images of the team concerned, the same number of times.
A coarray established before change team stays accessible inside it; its cosubscripts then count images of the current team. A
named construct entity can re-declare the codimensions with coarray-association-list (codimension-decl => selector), which
works as in associate. A child team can allocate and deallocate allocatable coarrays collectively among its own images. An allocatable
coarray that was unallocated when the construct started and is allocated at end team is deallocated by it (11.1.5.2).
The example splits four images into a left and a right half, which is how a plate would be handed to two groups, and then makes
each half compute its own total with a plain co_sum:
program teams
use, intrinsic :: iso_fortran_env, only: team_type
implicit none
type(team_type) :: half, where_am_i
integer :: me, color, my_total[*], in_team, team_id, n_in_team
integer :: total_seen[*]
me = this_image()
color = merge(1, 2, me <= num_images()/2) ! left half = team 1, right half = team 2
form team (color, half, new_index=me - (color - 1)*(num_images()/2)) ! all images, collectively
where_am_i = get_team() ! the current (here: initial) team
if (me == 1) print '(a,i0,a,i0)', 'initial team: team_number()=', team_number(), ' num_images()=', num_images()
change team (half) ! coarrays declared outside stay accessible
in_team = this_image()
team_id = team_number()
n_in_team = num_images()
total_seen = me ! 'me' is the image index in the initial team
call co_sum(total_seen) ! a collective acts on the current team only
sync team (get_team()) ! synchronize the current team (sync all would too)
my_total = total_seen
end team ! back in the initial team; implicit synchronization
sync all
if (me == 1) then
print '(a,i0,a,i0,a,i0)', 'after end team, image 1 sees the initial team again: ', this_image(), ' of ', num_images()
print '(a,i0)', 'sum over team 1: ', my_total[1]
print '(a,i0)', 'sum over team 2: ', my_total[num_images()]
print '(a,l1)', 'where_am_i has team_number -1: ', team_number(where_am_i) == -1
print '(a,i0,a,i0,a,i0)', 'last local values: team_id=', team_id, ' in_team=', in_team, ' n_in_team=', n_in_team
end if
end program teams
Output (4 images):
initial team: team_number()=-1 num_images()=4
after end team, image 1 sees the initial team again: 1 of 4
sum over team 1: 3
sum over team 2: 7
where_am_i has team_number -1: T
last local values: team_id=1 in_team=1 n_in_team=2
The two sums are for images 1 and 2 (1 + 2 = 3) and images 3 and 4 (3 + 4 = 7): the co_sum inside change team never saw the other half. The statement end team
restored the initial team, in which team_number() is -1 again. Three details:
-
The image numbering of a new team is processor dependent unless
new_index=is given. Without it, a probe run with four images (the same program withoutnew_index=) made global image 3 image 2 of team 2 and global image 4 image 1, and a six-image run made global image 2 image 3 of team 1 and global image 3 image 2. Code that indexes a coarray inside the team bythis_image()should say what it wants withnew_index=. -
sync allinside the construct synchronizes the current team only; the images of the other half are not involved. Afterend teamthe initial team is the current team again, sosync allthere involves all four images. -
Execution of
change teamandend teameach include a synchronization of the new team. The standard’s note (11.1.5.2) is that async teamof the parent team immediately afterchange team, or async allafterend team, is how to wait for the whole of the original team.
Team-aware image selectors
A cosubscript list selects an image; an image selector may also carry team= or team_number= (9.6, C934: not both):
-
With no specifier, the team of the selector is the current team: the cosubscript is an image index of the current team.
-
team=team_value: the team shall be the current team or an ancestor; the object shall be an established coarray in that team. -
team_number=n: when the current team is not the initial team,nshall be the team number of a sibling team of the current team, and the object shall be a coarray established in the parent of the current team (or an associating entity of thechange teamconstruct). In the initial team,nshall be the team number of the initial team,-1.
program team_selectors
use, intrinsic :: iso_fortran_env, only: team_type, initial_team, parent_team, current_team
implicit none
type(team_type) :: half
integer :: me, color, x[*]
me = this_image()
color = merge(1, 2, me <= num_images()/2)
form team (color, half, new_index=me - (color - 1)*(num_images()/2))
x = 100*me ! x is established in the initial team
sync all
change team (half)
sync all
if (team_number() == 2 .and. this_image() == 1) then
print '(a,i0,a,i0)', 'global image ', me, ' is image 1 of team ', team_number()
print '(a,i0)', 'x[1] = ', x[1] ! image 1 of MY team
print '(a,i0)', 'x[1, team=get_team(initial_team)] = ', x[1, team=get_team(initial_team)] ! image 1 of the initial team
print '(a,i0)', 'x[2, team_number=2] = ', x[2, team_number=2] ! my own team, by number
print '(a,i0)', 'num_images(get_team(parent_team)) = ', num_images(get_team(parent_team))
print '(a,i0)', 'image_index(x, [1], get_team(initial_team)) = ', image_index(x, [1], get_team(initial_team))
end if
end team
end program team_selectors
Output (4 images):
global image 3 is image 1 of team 2
x[1] = 300
x[1, team=get_team(initial_team)] = 100
x[2, team_number=2] = 400
num_images(get_team(parent_team)) = 4
image_index(x, [1], get_team(initial_team)) = 1
Global image 3 is image 1 of team 2: x[1] is the first image of its own team, global image 3, which holds 300; the selector with
team=get_team(initial_team) reaches global image 1 (100); and x[2, team_number=2] is the second image of its own team, global image 4 (400).
The standard also allows a sibling’s number. That is the case the same compilers do not run (a deliberately standard-conforming program,
rejected by the library at run time):
program sibling_selector
use, intrinsic :: iso_fortran_env, only: team_type
implicit none
type(team_type) :: half
integer :: me, color, x[*]
me = this_image()
color = merge(1, 2, me <= num_images()/2)
form team (color, half, new_index=me - (color - 1)*(num_images()/2))
x = 100*me
sync all
change team (half)
sync all
if (team_number() == 1 .and. this_image() == 1) print '(a,i0)', 'x in sibling team 2, image 1: ', x[1, team_number=2]
end team
end program sibling_selector
GNU Fortran 16.2 compiles it without a warning. At run time on 4 images the library prints:
Fortran runtime error: requested team not found
ERROR: Image 1(pid: 15) failed with signal 0, exitstatus 1.
and the remaining images then do not finish on their own: the run had to be killed by the 60-second timeout. Under C.7.7 of the standard
("Accessing coarrays in sibling teams") this access is valid, so this is a limitation of the shared-memory library in
this release. While it stands, reach a sibling’s data by publishing it in a coarray of the parent team and selecting
with team= (as in the previous example), or by an x[...] outside the construct. The selector with the team number of the current team,
x[2, team_number=2] above, works.
OpenCoarrays 2.10.3 could not be used for the teams examples: building 02_teams.f90 and 06_team_selectors.f90 with caf fails to link with
undefined reference to _gfortran_caf_get_team, because that library predates the teams support in GCC 16. A simpler
program with form team and change team only crashed with a segmentation fault at run time. Flang 23 refuses teams: by default with the
"Multi-image features are experimental" error; with -fcoarray, it prints for example
not yet implemented: coarray: lowering a reference to a coarray object; for the form-team, change-team and team-number statements the
objects it links against are prif_* runtime entry points (the Parallel Runtime Interface for Fortran, which the
Caffeine library implements), and the build ended with undefined references to
_QMprifPprif_form_team and _QMprifPprif_change_team.
Events
An event is a counter on one image that other images increment, with the waiting image able to block until the count is high
enough. It is the Fortran way to say "this data is ready" without a global barrier, and it orders segments in the same way as
sync images (11.7.8: the segments before k posts precede the segments after the wait that satisfies them).
| Item | Meaning | Clause |
|---|---|---|
|
from |
16.10.2.10 |
|
atomically adds one to the count on image |
11.7.7 |
|
the event variable shall not be coindexed: an image waits only on its own. Waits until the count is at least |
11.7.8 |
|
returns the current count (atomically with respect to posts; |
16.9.82 |
Because event wait subtracts the threshold, the count is the number of posts not yet consumed: ten posts and two plain waits leave a count of eight
(the example in 16.9.82). A count is only a snapshot when other images may still post.
Segments are ordered by an event: an image that wrote data, then posted, precedes the receiver’s segment that follows the wait. This is the
guarantee that makes the typical hand-off safe, with no sync memory or barrier needed. The sequence below is the handshake the next program
performs; it also shows an acknowledgement in the opposite direction, which the producer needs before it may reuse the buffer.
program event_handshake
use, intrinsic :: iso_fortran_env, only: event_type
implicit none
integer, parameter :: n = 4
real :: halo(n)[*] ! the data window on every image
type(event_type) :: data_ready[*] ! posted on the consumer when the data has arrived
type(event_type) :: consumed[*] ! posted on the producer when the consumer is done
integer :: count_left
if (num_images() < 2) error stop 'needs two images'
if (this_image() == 1) then
halo(:)[2] = [1.0, 2.0, 3.0, 4.0] ! one-sided put into image 2
event post (data_ready[2]) ! orders the put before the consumer's read
event wait (consumed) ! wait for the acknowledgement
call event_query(consumed, count_left)
print '(a,i0)', 'producer: acknowledged, event count after wait = ', count_left
else if (this_image() == 2) then
event wait (data_ready)
print '(a,4f5.1)', 'consumer: halo = ', halo
event post (consumed[1])
end if
end program event_handshake
Output (4 images, images 3 and 4 do nothing):
consumer: halo = 1.0 2.0 3.0 4.0
producer: acknowledged, event count after wait = 0
The data is written before the post, and the consumer reads it after its wait, so it sees 1.0 2.0 3.0 4.0. The count is back to zero after the
wait. OpenCoarrays 2.10.3 printed the same two lines; Flang 23 with -fcoarray stopped with not yet implemented: coarray: lowering a reference to a coarray object.
until_count gathers several posts with one wait, which is how a leader learns that a whole group is ready:
program event_count
use, intrinsic :: iso_fortran_env, only: event_type
implicit none
type(event_type) :: arrived[*]
integer :: n
if (this_image() /= 1) then
event post (arrived[1]) ! every other image reports once
else
event wait (arrived, until_count=num_images() - 1) ! returns when the count reaches num_images()-1
call event_query(arrived, n)
print '(a,i0,a,i0)', 'image 1 saw ', num_images() - 1, ' posts; count left = ', n
end if
end program event_count
Output (4 images):
image 1 saw 3 posts; count left = 0
Notes:
-
An event post is a one-way, non-blocking release: it never waits for the receiver. Always wait on the receiver’s own variable.
-
With
stat=onevent postan error is reported as for any image control statement (11.7.11), includingstat_failed_imagewhen the target image has failed;event waitreports another positive value on error. -
A named variable of type
event_typeshall be a coarray (C1604). The count is updated as if byatomic_add, so an event coarray may be referenced in segments unordered with respect to each other, like an atomic (16.10.2.10);notify_typeis specified the same way (16.10.2.22).
Put with notify (Fortran 2023)
A put followed by an event post is two operations: the data transfer and the signal. Fortran 2023 fuses them: an assignment to a coindexed
variable can carry notify= in its image selector, which atomically increments the notify count of a notify_type variable on the target
image, and the receiver blocks with notify wait. Introduced in Fortran 2023 (Introduction of J3/23-007r1), described in 11.6, in 9.6 (C933: the
notify= specifier may appear only in the designator of the variable of an intrinsic assignment statement) and in 16.10.2.22 (notify_type, whose
count behaves like an atomic_int_kind integer updated as if by atomic_add).
The intended form of the handshake of the previous section, for the data direction only, is:
program notify_demo
use, intrinsic :: iso_fortran_env, only: notify_type
implicit none
type(notify_type) :: done[*]
real :: halo(4)[*]
if (this_image() == 1) then
halo(:)[2, notify=done] = [1.0, 2.0, 3.0, 4.0] ! Fortran 2023
else if (this_image() == 2) then
notify wait (done)
print *, halo
end if
end program notify_demo
Output of gfortran -fcoarray=lib -std=f2023 (GNU Fortran 16.2): rejected, because neither the type nor the syntax exists in this release:
11_notify_rejected.f90:2:42:
2 | use, intrinsic :: iso_fortran_env, only: notify_type
| 1
Error: Symbol 'notify_type' referenced at (1) not found in intrinsic module ISO_FORTRAN_ENV
11_notify_rejected.f90:4:20:
4 | type(notify_type) :: done[*]
| 1
Error: Derived type 'notify_type' at (1) is being used before it is defined
11_notify_rejected.f90:7:22:
7 | halo(:)[2, notify=done] = [1.0, 2.0, 3.0, 4.0] ! Fortran 2023
| 1
Error: Invalid form of coarray reference at (1)
This example was not run: the text is checked against the standard (11.6, 9.6 and 16.10.2.22), not against a compiler. None of the compilers
available for this page accepts it. Intel ifx, NAG and the other compilers were not available, so nothing is claimed about them. Until your compiler
supports it, use the event handshake above: event post after the put, which orders the same two segments.
Atomic subroutines
An atomic subroutine (16.5) performs an action on its atom argument indivisibly. Any two atomic actions on the same object in unordered
segments behave as if one finished before the other began, in a processor-dependent order, without needing image control statements. They are the
building block for custom synchronization (counters, flags, tickets). The atom is a scalar coarray or coindexed object of type integer
of kind atomic_int_kind or logical of kind atomic_logical_kind (both from iso_fortran_env, 16.10.2.2 and 16.10.2.3). Only atom is accessed
atomically; the other arguments are ordinary.
| Subroutine | Effect | Clause |
|---|---|---|
|
sets |
16.9.31 |
|
copies |
16.9.37 |
|
|
16.9.28, 16.9.29, 16.9.36, 16.9.38 |
|
the same, and |
16.9.32 to 16.9.35 |
|
if |
16.9.30 |
atomic_define and atomic_ref are older (their stat argument is among the Fortran 2018 additions from TS 18508, Annex C.1); atomic_add and the rest of the family arrived
in Fortran 2018 (Annex C.1). stat is zero on success; stat_failed_image if atom is on a failed image; otherwise a processor-dependent positive
value; without it an error terminates the program (16.5).
Atomics synchronize nothing by themselves: an atomic_define of the initial value on one image and an atomic_add on another, in unordered segments, are in an
undetermined order. The rules of the standard (16.5) are the ones that matter: atomic operations shall make asynchronous progress (a loop that polls with atomic_ref
will eventually see the value), and the sequence of changes seen by another image can appear in a different order from the one the defining image performed.
The program below does what the named plain coarray accesses cannot: a shared counter, a ticket dispenser and an election, all by atomic_* on
image 1, followed by a flag polled by the last image.
program atomics
use, intrinsic :: iso_fortran_env, only: atomic_int_kind, atomic_logical_kind
implicit none
integer(atomic_int_kind) :: counter[*], ticket[*], leader[*]
logical(atomic_logical_kind) :: done[*]
integer(atomic_int_kind) :: mine, old, seen
logical :: flag
integer :: stat, me, k
integer(atomic_int_kind), allocatable :: tickets(:)[:]
me = this_image()
allocate (tickets(num_images())[*])
call atomic_define(counter, 0_atomic_int_kind) ! initialise your own copy ...
call atomic_define(ticket, 0_atomic_int_kind)
call atomic_define(leader, 0_atomic_int_kind)
call atomic_define(done, .false.)
sync all ! ... then make it visible before anyone uses it
call atomic_add(counter[1], 10_atomic_int_kind) ! everybody adds 10 to image 1's counter
call atomic_fetch_add(ticket[1], 1_atomic_int_kind, mine) ! take a unique ticket number
tickets(me)[1] = mine ! publish it (one distinct element per image)
call atomic_cas(leader[1], old, 0_atomic_int_kind, int(me, atomic_int_kind)) ! first one wins
sync all
if (me == 1) then
call atomic_ref(seen, counter, stat)
print '(a,i0,a,i0)', 'counter = ', seen, ' stat = ', stat
print '(a,l1)', 'every ticket 0..3 handed out exactly once: ', &
all([(count(tickets == k) == 1, k=0, num_images() - 1)])
call atomic_ref(seen, leader)
print '(a,l1)', 'one leader elected, index in range: ', seen >= 1 .and. seen <= num_images()
call atomic_define(done[num_images()], .true.)
end if
if (me == num_images()) then
do ! spin until image 1 signals
call atomic_ref(flag, done)
if (flag) exit
end do
print '(a)', 'last image saw the flag set by image 1'
end if
end program atomics
Output (4 images):
counter = 40 stat = 0
every ticket 0..3 handed out exactly once: T
one leader elected, index in range: T
last image saw the flag set by image 1
Four images each added 10 (40), took a different ticket (the check prints T when every number from 0 to 3 was handed out exactly once; which image got which number differs from run to run),
and one won the election. The tickets are published with ordinary assignments to distinct elements of a coarray, and sync all orders them before they are read. With OpenCoarrays 2.10.3 the counter and the election
were right, but the ticket check printed F (the published element values were garbage); that was not investigated, so the example is portable across the standard, not shown to
be portable across these libraries. Flang 23 stops with not yet implemented: coarray: intrinsic atomic_define.
Failed and stopped images
An image stops when it begins normal termination (stop, or the end of the program), and fails when it terminates abnormally in a way the
program may survive, which a program can simulate with the fail image statement (11.5). The rest of the program can then learn what happened and continue with the
remaining images. Detecting this relies on stat= in image control statements and collectives:
| Item | Meaning | Clause |
|---|---|---|
|
if an involved image has stopped, |
11.7.11 |
|
increasing indices of the images known by the invoking image to have failed (a zero-sized array if none) |
16.9.87 |
|
the same for stopped images |
16.9.199 |
|
|
16.9.108 |
|
named constants of |
16.10.2.28 to 16.10.2.33 |
Image failure is a model: the standard defines what a program can observe, and the implementation decides what can fail. The two
programs below follow it with the libcaf_shmem library.
program failed_stopped
use, intrinsic :: iso_fortran_env, only: int64, stat_failed_image, stat_stopped_image
implicit none
integer :: stat, i
if (this_image() == 2) stop 0 ! image 2 ends normally and early: it is "stopped"
call pause_seconds(2) ! give image 2 time to finish (see the text for why)
sync all (stat=stat) ! must not wait for a stopped image forever
if (this_image() == 1) then
print '(a,l1)', 'stat == stat_stopped_image: ', stat == stat_stopped_image
print '(a,*(i0,1x))', 'stopped_images(): ', stopped_images()
print '(a,*(i0,1x))', 'failed_images(): ', failed_images()
do i = 1, num_images()
print '(a,i0,a,i0)', 'image_status(', i, ') = ', image_status(i)
end do
print '(a,i0,a,i0)', 'stat_stopped_image = ', stat_stopped_image, ', stat_failed_image = ', stat_failed_image
end if
contains
subroutine pause_seconds(s)
integer, intent(in) :: s
integer(int64) :: t0, t, rate
call system_clock(t0, rate)
do
call system_clock(t)
if (t - t0 >= s*rate) exit
end do
end subroutine pause_seconds
end program failed_stopped
Output (4 images; the line STOP 0 comes from image 2 as it stops, and the position of that line may differ):
STOP 0
stat == stat_stopped_image: T
stopped_images(): 2
failed_images():
image_status(1) = 0
image_status(2) = 6000
image_status(3) = 0
stat_stopped_image = 6000, stat_failed_image = 6001
The statement fail image is the failure case:
program fail_image_demo
use, intrinsic :: iso_fortran_env, only: int64, stat_failed_image
implicit none
integer :: stat
if (this_image() == 2) fail image ! simulate a crash of image 2
call pause_seconds(2)
sync all (stat=stat)
if (this_image() == 1) then
print '(a,l1)', 'stat == stat_failed_image: ', stat == stat_failed_image
print '(a,*(i0,1x))', 'failed_images(): ', failed_images()
print '(a,i0)', 'image_status(2) = ', image_status(2)
end if
contains
subroutine pause_seconds(s)
integer, intent(in) :: s
integer(int64) :: t0, t, rate
call system_clock(t0, rate)
do
call system_clock(t)
if (t - t0 >= s*rate) exit
end do
end subroutine pause_seconds
end program fail_image_demo
Output (4 images):
IMAGE FAILED!
stat == stat_failed_image: T
failed_images(): 2
image_status(2) = 6001
Findings that apply to GNU Fortran 16.2 with libcaf_shmem, observed here:
-
The
pause_secondswait beforesync all (stat=stat)is not decoration. Async allreaching an image that has not yet stopped is a barrier, and in the runs made here a version of the program with no pause usually did not finish (it had to be killed by the timeout) instead of reportingstat_stopped_image. A program that needs to survive a failure should not rely on timing: let the survivors learn about a failure through a mechanism that is not a barrier the failed image was supposed to join, for examplestat=onevent post, or pollingimage_statusin a loop. -
stat_stopped_imageis6000andstat_failed_imageis6001in this implementation; other compilers use other values, so compare with the named constants. -
Assigning the result of
stopped_images()orfailed_images()to an allocatable array (gone = stopped_images()) gave an array of size 2,2 0, when one image had stopped, while using the call directly inprintorsize()gave the right size 1. The file10_stopped_images_assignment.f90in the sources reproduces it. Use the result as an actual argument or index expression if you can, and check the size when you cannot. -
fail imagein OpenCoarrays 2.10.3 terminates the MPI job (BAD TERMINATION OF ONE OF YOUR APPLICATION PROCESSES), andstop 0on one image reachedMPI_Abort: the run-through-failure model needs the fault-tolerance support that this build lacks. -
The values of the other
stat_constants and theerrmsgtext are processor dependent; the page does not copy them.
Coarray components in arrays and allocatables (Fortran 2023)
Up to Fortran 2018 a variable whose type has a coarray component had to be a scalar, neither allocatable nor a pointer nor a coarray (the GNU Fortran message below still says so). The Introduction
of J3/23-007r1 lists the relaxation: "A data object with a coarray component can be an array or allocatable." What remains in Fortran 2023 is that the variable shall not be a pointer, a coarray or a function
result (C825), shall be an associate name, a dummy argument or have the allocatable or save attribute (C826), and that a coarray component shall be allocatable with a deferred coshape (C751). A type that holds the
slab of the plate can then be used in an array of slabs. The Fortran 2023 form:
program coarray_components
implicit none
type :: slab_t
real, allocatable :: u(:)[:]
end type slab_t
type(slab_t) :: slabs(2) ! array of a type with a coarray component
integer :: k
do k = 1, 2
allocate (slabs(k)%u(3)[*])
slabs(k)%u = real(this_image()*10 + k)
end do
sync all
end program coarray_components
Output of gfortran -fcoarray=lib -std=f2023 (GNU Fortran 16.2): rejected:
12_coarray_component_array_rejected.f90:6:19:
6 | type(slab_t) :: slabs(2) ! array of a type with a coarray component
| 1~~~~~~~
Error: Variable 'slabs' at (1) with coarray component shall be a nonpointer, nonallocatable scalar, which is not a coarray
The message states the older restriction, so this release does not implement the relaxation. Not run; the example is checked against the standard (7.5.4.3, 8.5.6 and the Introduction), not against a compiler. Until a compiler catches up, declare one scalar variable of the type per slab, or keep an allocatable coarray of the data itself next to an ordinary array of descriptors.
Fortran 2028 preview: team-scoped and non-blocking collectives
Fortran 2028 is at the committee-draft stage; ISO/IEC 1539-1 for it is not published, and this section is based on the J3 working draft J3/26-007r1 of 3 March 2026 (the WG5 committee draft N2258 is not a public document). Anything below can still change. The relevant changes in the draft’s Introduction and 17.6 are:
-
The collective subroutines gain an optional
teamargument. Its value shall be the current team, an ancestor, or a team whose parent is the current team, and identify the same team on all images taking part, so a collective can be run on a team without achange team. -
They gain an optional
completionargument of the new typecompletion_type, which turns the call into an asynchronous collective: the call returns after initiating the operation, the new subroutinecomplete(completion [, finished])waits for it, and meanwhile the data argument (andstatanderrmsg) must not be touched, like the buffer of an asynchronous input operation (draft 17.6, 17.9.64). -
New collective prefix reductions (
co_sum_prefix_inclusive,co_sum_prefix_exclusive,co_reduce_prefix_inclusive,co_reduce_prefix_exclusive) and the corresponding array functions.
A program that uses team= today is rejected, which I checked for completeness:
program team_argument
use, intrinsic :: iso_fortran_env, only: team_type
implicit none
type(team_type) :: t
real :: x
form team (1, t)
x = 1.0
call co_sum(x, team=t) ! TEAM= is a Fortran 2028 draft feature
end program team_argument
Output of gfortran -fcoarray=lib -std=f2023 (GNU Fortran 16.2):
13_team_argument_rejected.f90:8:67:
8 | call co_sum(x, team=t) ! TEAM= is a Fortran 2028 draft feature
| 1
Error: Cannot find keyword named 'team' in call to 'co_sum' at (1)
Not run, not available: the interface is shown only as "upcoming". Do not write production code against it; where you need a team-scoped
collective now, put it in a change team construct as shown above.
Where the books are outdated
| Topic | What changed |
|---|---|
Teams, events, collectives, atomics, failed images |
books written against Fortran 2008 describe coarrays with |
|
the argument is |
Teams: team number of the initial team |
the initial team has team number |
Image numbering in a team |
only guaranteed by |
Put with notify, coarray components in arrays and allocatables |
Fortran 2023; a book that predates it shows the event-based pattern and the scalar-only rule |
Team-scoped and non-blocking collectives |
only in the Fortran 2028 working draft |
Compilers |
older books say coarrays need a vendor compiler or OpenCoarrays; GCC 16 has native shared-memory coarrays and teams, and OpenCoarrays 2.10.3 (MPI based) does not link the teams of the same compiler |
Standard version provenance and compiler support
| Feature | Introduced | Support checked on 2026-10-19 |
|---|---|---|
Events, collectives, atomics ( |
Fortran 2018, from TS 18508:2015 (Annex C.1) |
gfortran 16.2 with |
|
before Fortran 2018 (edition not confirmed here); the |
gfortran 16.2 and OpenCoarrays 2.10.3 |
|
changes of Fortran 2018 relative to TS 18508 (Annex C.1) |
gfortran 16.2 |
Put with notify |
Fortran 2023 |
rejected by gfortran 16.2 ( |
Array or allocatable object with a coarray component |
Fortran 2023 |
rejected by gfortran 16.2 |
|
Fortran 2028 working draft J3/26-007r1 |
rejected by gfortran 16.2 |
Not checked here, because no compiler for them is installed: Intel ifx, NAG, HPE Cray and NVIDIA HPC compilers. See History, Standards and Compilers for the per-compiler support table.
Pitfalls
-
Reading
aon the wrong image afterresult_image=. The variable is undefined elsewhere. -
Mismatched collectives. Every image of the team must call the same collective in the same order; a collective under
if (this_image() == 1)deadlocks or terminates. -
Assuming image numbers inside a team. Use
new_index=or translate withimage_index(x, [k], team). -
Cosubscripts after
change team. They count images of the current team; the global index needsteam=get_team(initial_team). -
Sibling selectors. Standard-conforming but refused by
libcaf_shmemin GCC 16.2 at run time. -
Waiting on someone else’s event.
event waittakes the executing image’s own variable; post to the other one. -
Timing-based failure demonstrations. A barrier that includes a stopped image may not return in the library used here; make the survivors poll
image_statusor usestat=on something that is not a barrier. -
Treating atomics as synchronization. They order nothing outside the atom; publish other data with
syncor an event. -
Plain reads of an atomic. Use
atomic_ref; a plain reference is not atomic. -
Comparing
statwith a literal.stat_failed_imageandstat_stopped_imagehave processor-dependent values. -
Writing against a feature your compiler lacks. Put with notify, component coarrays in arrays and
team=collectives are not in gfortran 16.2; keep the event pattern behind a procedure so the code can change later. -
Floating-point
co_sum. The order of additions is not fixed; compare with a tolerance.
See also do concurrent and GPU Offload,
OpenMP and OpenACC and
MPI with mpi_f08 for the other ways to the same parallelism, and
Error Handling and Program Termination for error stop and the termination of images.