This is designed as a question to the programming community as a whole. Thus I will refrain from any specific programming language.
This is a very important discussion to have due to the rise of heavily multi-core processors (cough cough Ryzen), and creating programs which can actually utilize all of those cores will become critical as the overhead for running multiple copies of a program instead of one parallelly scalable one seems to be a common approach to utilizing current multithreading capabilities of servers.
What is the best way to handle data which needs to be shared across multiple running threads without having access to collisions or bottlenecks due to accessing shared memory?
This will heavily depend on the workload, much like how one could argue linked lists are better than vectors.
While I can acknowledge the downfalls of object-oriented programming, and wish functional programming was used more so people can realise where it is best used, sadly I think one of the optimal solutions does from for OO.
Let's say you're shared object (counter) is a thread locked class (class methods can only be executed in the thread the object was originally initialized in). This means when another thread executes one of this object's methods it is added to the owning thread's task queue.
Now let's say that you have another two threads (neither of which own counter) which are doing some big number-crunching that generates numbers that need to be added to the counter. Each of these threads can keep generating these numbers and throwing off new tasks for the owner of counter, and as long as the function call is handled asynchronously the two threads can continue their number crunching.
These two threads are able to alter the shared memory without them waiting for any atomic operations (depending on how schedules are managed), instead, they are just generating some callback handles and continuing on their way which creates an ideal situation with no slowdowns.
Only when one of the two threads attempts to get the value of the counter (through a method) would it then have to wait. This can then also create an interesting scenario, if the owner of counter was busy with another task(s) and hadn't processed all of the add methods, the get would have to wait until they had been processed before it gets a response. This means that by the time it gets the returned value the add methods will have been correctly applied effectively making a kind of lazy calculation scenario. And also any new add methods that were called after the get was will then be processed after the get, meaning the value will not be peaking forwards in time for when it was called.
(Presuming that the scheduler never alters the order of the tasks).
Obviously this can be applied to much more complex shared objects making it more useful, however, this is an interesting way of removing a lot of data race uncertainties as well as atomic calls (though this also depends on how the threads interact with the scheduler). If a programmer completely encapsulates the shared object's attributes there will be no data race conditions no matter how poorly the object is used, thus it is an interesting way of wrapping safeties around shared data.
I presume there are alternative methods for solving such a problem, and there will also be people who are disgusted by my preposition. I would like to hear all of your comments so I can learn about different multi-threading approaches and hopefully spark an interesting discussion of what really needs to be had with the coming computer hardware.