Because the code can be suspended at any point, you can't ever assume that things won't change "by themselves". When programming with threads, you waste a lot of time thinking about how your program should deal with things changing.
Imagine a game server is processing a player's attack on another player. Something like this:
if (playerInMeleeRange(attacker, victim)) { const damage = calculateAttackDamage(attacker, victim); if (victim.health <= damage) { // attacker gets whatever the victim was carrying as loot const loot = victim.getInventoryItems(); attacker.addInventoryItems(loot); victim.removeInventoryItems(loot); victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon} and you die!"); victim.setDead(); } else { victim.health -= damage; victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon}!"); } attacker.markAsKiller(); }
Three months later, a player discovers that by getting killed and logging off precisely when
attacker.addInventoryItems
is running, thenvictim.removeInventoryItems
will fail, he can keep his items and the attacker also gets a copy of his items. He does this several times, creating a million tonnes of gold out of thin air and crashing the game's economy.Alternatively, the attacker can log out while the game is sending a message to the victim, and he won't get a "murderer" tag above his head, so his next victim won't run away from him.
Because the code can be suspended at any point, you need to use locks everywhere when manipulating data structures. I gave an example above that has obvious consequences in a game, but it can be more subtle. Consider adding an item to the start of a linked list:
newItem.nextItem = list.firstItem; list.firstItem = newItem;
This isn't a problem if you say that threads can only be suspended when they're doing I/O, and not at any point. But I'm sure you can imagine a situation where there's an I/O operation - such as logging:
for (player = playerList.firstItem; player != null; player = item.nextPlayer) { debugLog("${item.name} is online, they get a gold star"); // Oops! The player might've logged out while the log message was being written to disk, and now this will throw an exception and the remaining players won't get their gold stars. // Or the list might've been rearranged and some players might get two and some players might get none. player.addInventoryItem(InventoryItems.GoldStar); }
Because the code can be suspended at any point, there could potentially be a lot of state to save. The system deals with this by giving each thread an entirely separate stack. But the stack is quite big, so you can't have more than about 2000 threads in a 32-bit program. Or you could reduce the stack size, at the risk of making it totoo small.
Because the code can be suspended at any point, you can't ever assume that things won't change "by themselves". When programming with threads, you waste a lot of time thinking about how your program should deal with things changing.
Imagine a game server is processing a player's attack on another player. Something like this:
if (playerInMeleeRange(attacker, victim)) { const damage = calculateAttackDamage(attacker, victim); if (victim.health <= damage) { // attacker gets whatever the victim was carrying as loot const loot = victim.getInventoryItems(); attacker.addInventoryItems(loot); victim.removeInventoryItems(loot); victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon} and you die!"); victim.setDead(); } else { victim.health -= damage; victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon}!"); } attacker.markAsKiller(); }
Three months later, a player discovers that by getting killed and logging off precisely when
attacker.addInventoryItems
is running, thenvictim.removeInventoryItems
will fail, he can keep his items and the attacker also gets a copy of his items. He does this several times, creating a million tonnes of gold out of thin air and crashing the game's economy.Alternatively, the attacker can log out while the game is sending a message to the victim, and he won't get a "murderer" tag above his head, so his next victim won't run away from him.
Because the code can be suspended at any point, you need to use locks everywhere when manipulating data structures. I gave an example above that has obvious consequences in a game, but it can be more subtle. Consider adding an item to the start of a linked list:
newItem.nextItem = list.firstItem; list.firstItem = newItem;
This isn't a problem if you say that threads can only be suspended when they're doing I/O, and not at any point. But I'm sure you can imagine a situation where there's an I/O operation - such as logging:
for (player = playerList.firstItem; player != null; player = item.nextPlayer) { debugLog("${item.name} is online, they get a gold star"); // Oops! The player might've logged out while the log message was being written to disk, and now this will throw an exception and the remaining players won't get their gold stars. // Or the list might've been rearranged and some players might get two and some players might get none. player.addInventoryItem(InventoryItems.GoldStar); }
Because the code can be suspended at any point, there could potentially be a lot of state to save. The system deals with this by giving each thread an entirely separate stack. But the stack is quite big, so you can't have more than about 2000 threads in a 32-bit program. Or you could reduce the stack size, at the risk of making it to small.
Because the code can be suspended at any point, you can't ever assume that things won't change "by themselves". When programming with threads, you waste a lot of time thinking about how your program should deal with things changing.
Imagine a game server is processing a player's attack on another player. Something like this:
if (playerInMeleeRange(attacker, victim)) { const damage = calculateAttackDamage(attacker, victim); if (victim.health <= damage) { // attacker gets whatever the victim was carrying as loot const loot = victim.getInventoryItems(); attacker.addInventoryItems(loot); victim.removeInventoryItems(loot); victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon} and you die!"); victim.setDead(); } else { victim.health -= damage; victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon}!"); } attacker.markAsKiller(); }
Three months later, a player discovers that by getting killed and logging off precisely when
attacker.addInventoryItems
is running, thenvictim.removeInventoryItems
will fail, he can keep his items and the attacker also gets a copy of his items. He does this several times, creating a million tonnes of gold out of thin air and crashing the game's economy.Alternatively, the attacker can log out while the game is sending a message to the victim, and he won't get a "murderer" tag above his head, so his next victim won't run away from him.
Because the code can be suspended at any point, you need to use locks everywhere when manipulating data structures. I gave an example above that has obvious consequences in a game, but it can be more subtle. Consider adding an item to the start of a linked list:
newItem.nextItem = list.firstItem; list.firstItem = newItem;
This isn't a problem if you say that threads can only be suspended when they're doing I/O, and not at any point. But I'm sure you can imagine a situation where there's an I/O operation - such as logging:
for (player = playerList.firstItem; player != null; player = item.nextPlayer) { debugLog("${item.name} is online, they get a gold star"); // Oops! The player might've logged out while the log message was being written to disk, and now this will throw an exception and the remaining players won't get their gold stars. // Or the list might've been rearranged and some players might get two and some players might get none. player.addInventoryItem(InventoryItems.GoldStar); }
Because the code can be suspended at any point, there could potentially be a lot of state to save. The system deals with this by giving each thread an entirely separate stack. But the stack is quite big, so you can't have more than about 2000 threads in a 32-bit program. Or you could reduce the stack size, at the risk of making it too small.
They do (well, most of them). The feature you're looking for is called threads.
Threads have their own problems however:
Because the code can be suspended at any point, you can't ever assume that things won't change "by themselves". When programming with threads, you waste a lot of time thinking about how your program should deal with things changing.
Imagine a game server is processing a player's attack on another player. Something like this:
if (playerInMeleeRange(attacker, victim)) { const damage = calculateAttackDamage(attacker, victim); if (victim.health <= damage) { // attacker gets whatever the victim was carrying as loot const loot = victim.getInventoryItems(); attacker.addInventoryItems(loot); victim.removeInventoryItems(loot); victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon} and you die!"); victim.setDead(); } else { victim.health -= damage; victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon}!"); } attacker.markAsKiller(); }
Three months later, a player discovers that by getting killed and logging off precisely when
attacker.addInventoryItems
is running, thenvictim.removeInventoryItems
will fail, he can keep his items and the attacker also gets a copy of his items. He does this several times, creating a million tonnes of gold out of thin air and crashing the game's economy.Alternatively, the attacker can log out while the game is sending a message to the victim, and he won't get a "murderer" tag above his head, so his next victim won't run away from him.
Because the code can be suspended at any point, you need to use locks everywhere when manipulating data structures. I gave an example above that has obvious consequences in a game, but it can be more subtle. Consider adding an item to the start of a linked list:
newItem.nextItem = list.firstItem; list.firstItem = newItem;
This isn't a problem if you say that threads can only be suspended when they're doing I/O, and not at any point. But I'm sure you can imagine a situation where there's an I/O operation - such as logging:
for (player = playerList.firstItem; player != null; player = item.nextPlayer) { debugLog("${item.name} is online, they get a gold star"); // Oops! The player might've logged out while the log message was being written to disk, and now this will throw an exception and the remaining players won't get their gold stars. // Or the list might've been rearranged and some players might get two and some players might get none. player.addInventoryItem(InventoryItems.GoldStar); }
Because the code can be suspended at any point, there could potentially be a lot of state to save. The system deals with this by giving each thread an entirely separate stack. But the stack is quite big, so you can't have more than about 2000 threads in a 32-bit program. Or you could reduce the stack size, at the risk of making it to small.