For question 1, the answer is "dependent, but probably not." If you really only care that one value is not distorted, then yes, this is normal and you also don't need the memory order.
However, as a rule, this is a false assumption.
For questions 2 , 3, and 4, yes, it will work, but it will most likely use a lock for complex objects such as string (internally, for every access, without your knowledge). Only small objects, the size of which is approximately equal to one or two pointers, can usually be accessed / changed atomically without blocking. It also depends on your platform.
The big difference is that one of them successfully updates one or two values atomically. Suppose you have left and right values that limit the left and right borders, where the task will do some processing in the array. Suppose they are 50 and 100 respectively, and you change them to 101 and 150, each atomically. Thus, another thread picks up a change from 50 to 101 and starts performing calculations, sees that 101> 100, finishes and writes the result to a file. After that, you change the name of the output file, again, atomically.
Everything was atomic (and therefore more expensive than usual), but none of this was useful. The result is still erroneous and was also written in the wrong file.
This may not be a problem in your particular case, but usually it (and your requirements may change in the future). Usually you really want the complete set of changes to be atomic.
However, if you have either many or complex (or, like many, complex) updates like this, you might want to use one large (reader / writer) lock for the entire configuration anyway, so as it is more efficient than acquiring and releasing 20 or 30 locks or performing 50 or 100 atomic operations. However, keep in mind that in any case, blocking will greatly affect performance.
As stated in the comments above, I would rather make a deep copy of the configuration from a single thread that changes the configuration, and plan on updating the reference (common pointer) used by consumers as common tasks. This copy-modify-publish approach is a bit like working with MVCC databases (they also have the problem that locking kills their performance).
Changing the copy means that only readers get access to any general state, so synchronization is not required for readers or for any author. Reading and writing fast. The configuration set is exchanged only at clearly defined points at the moments when the set is guaranteed to be in a complete, consistent state, and the flows are guaranteed not to do anything else, so there can be no ugly surprises.
A typical task-driven application will look something like this (in C ++ pseudo-code):
// consumer/worker thread(s) for(;;) { task = queue.pop(); switch(task.code) { case EXIT: return; case SET_CONFIG: my_conf = task.data; break; default: task.func(task.data, &my_conf); // can read without sync } } // thread that interacts with user (also producer) for(;;) { input = get_input(); if(input.action == QUIT) { queue.push(task(EXIT, 0, 0)); for(auto threads : thread) thread.join(); return 0; } else if(input.action == CHANGE_SETTINGS) { new_config = new config(config); // copy, readonly operation, no sync // assume we have operator[] overloaded new_config[...] = ...; // I own this exclusively, no sync task t(SET_CONFIG, 0, shared_ptr<...>(input.data)); queue.push(t); } else if(input.action() == ADD_TASK) { task t(RUN, input.func, input.data); queue.push(t); } ... }