Swift makes sure that multiple accesses to the same area of memory don’t conflict, by requiring code that modifies a location in memory to have exclusive access to that memory.
A conflicting access to memory can occur when different parts of your code are trying to access the same location in memory at the same time, which can produce unpredictable or inconsistent behavior.
If you have conflicting access to memory from within a single thread, Swift guarantees that you’ll get an error at either compile time or runtime. For multithreaded code, use Thread Sanitizer to help detect conflicting access across threads.
A conflict can occur if you have two accesses that meet all of the following conditions.
- At least one is a write access or a nonatomic access.
- They access the same location in memory.
- Their durations overlap.
A write access changes the location in memory, but a read access doesn’t.
The duration of a memory access is either instantaneous or long-term.
An access is instantaneous if it’s not possible for other code to run after that access starts but before it ends.
Two instantaneous accesses can’t happen at the same time. Most memory access is instantaneous.
It’s possible for other code to run after a long-term access starts but before it ends, which is called overlap. A long-term access can overlap with other long-term accesses and instantaneous accesses.
Overlapping accesses appear primarily in code that uses in-out parameters in functions and methods or mutating methods of a structure.
Conflicting Access to In-Out Parameters
- A function has long-term write access to all of its in-out parameters.
- The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call.
If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.
Original variable that was passed as in-out can’t be accessed.
example :
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
Both number and stepSize refer to the same location in memory. The read access to stepSize overlaps with the write access to number, producing a conflict.
The conflict can be solved by :
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(©OfStepSize)
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
Another consequence of long-term write access to in-out parameters is that passing a single variable as the argument for multiple in-out parameters of the same function produces a conflict.
example :
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // OK
balance(&playerOneScore, &playerOneScore) // Error: conflicting accesses to playerOneScore
passing playerOneScore as the value for both parameters produces a conflict because it tries to perform two write accesses to the same location in memory at the same time.
Conflicting Access to self in Methods
- A mutating method on a structure has write access to self for the duration of the method call.
example :
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // OK
oscar.shareHealth(with: &oscar). // Error: conflicting accesses to oscar
The mutating method needs write access to self for the duration of the method, and the in-out parameter needs write access to teammate for the same duration.
Within the method, both self and teammate refer to the same location in memory and they overlap, producing a conflict.
Conflicting Access to Properties
- structures, tuples, and enumerations are value types.
- Mutating any piece of the value mutates the whole value, meaning read or write access to one of the properties requires read or write access to the whole value.
example :
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
A write access to the tuple element requires a write access to the entire tuple. This means there are two write accesses to playerInformation with durations that overlap, causing a conflict.
- same error appears for overlapping write accesses to the properties of a structure that’s stored in a global variable.
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error
When the scope of struct is changed to a local variable instead of a global variable, the compiler can prove that overlapping access to stored properties of the structure is safe:
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // OK
}
It's because of multiple threads could access the global variable at the same time. In the case of a local variable, that variable exists once per execution of the function. If multiple threads run someFunction() simultaneously, each thread has its own "oscar" variable, so there is no chance that thread 1's "oscar" variable access thread 2's oscar variable.
Top comments (0)