No. Not at all. Instantiate may NOT be called from within a separate thread. That an action is atomic does not by any means imply thread safety; rather, it means that a CPU core cannot suspend in the middle of its execution to concentrate on something else. Each atomic action blocks a thread until complete and represents a single term in the sequence, when you list actions in an interleaving.
Here’s an example to demonstrate how this could go horribly, horribly wrong:
Suppose Unity’s mainthread is in the middle of rendering the scene. It has come to the point where all vertices of all known objects have been sent to the graphics card, and the GPU is now in the process of passing vertices through the pipeline one by one. Every time an object is turned into pixels by the rasterizer, the graphics card compares the depth value of the fragments to the depths already recorded in the depth buffer, to see if the new object is closer to the camera, or perhaps occluded by an object that came through the pipeline at an earlier stage.
But now, all of a sudden, and concurrently in a separate thread, somebody calls Instantiate, and inserts a brand new object in the world. This totally fucks up the graphics card’s work, because:
A: The new object’s vertices haven’t even been uploaded to the graphics card yet.
B: The new object might have a renderer whose shader belongs to the Opaque queue, and maybe the graphics card is currently busy rendering the Transparent queue. This screws up the order in which objects should be rendered to account for alpha correctly.
C: Maybe the new object has scripts attached to it that are supposed to start executing, but Unity’s execution order might currently be in the phase where coroutines are executed. Now what? Is Unity supposed to execute the script’s Awake() and Start(), or perhaps wait till next frame? Or what?
D: + 100 other things that a pipelined rendering system is not tailored for.
So, how to get around this?
One answer is to organize the extra workload so that the variables and objects affected are not directly related to Unity’s rendering engine. This means, do not attempt to modify transforms, physics values, particle systems, and so on and so forth. Instead, perform calculations on non-Unity variables and objects, and then have Unity react to changes in these variables when it Updates its next frame. That way, you can split mathematically expensive calculations to a separate thread without messing with the sequence of things done by Unity.
Another answer is to attempt to split the instantiations into smaller chunks of work and then do it in coroutines. I.e., suppose your procedural code needs to instantiate 1000 boxes. Instead of writing code that loops from 1-1000 and call Instantiate on each iteration, write code that loops from 1-1000 and then yields for every 50 instantiations. Then you have effectively split the work over 20 frames instead of insisting on doing all 1000 in a single frame. This lets the rendering continue while work takes place.
How you choose to do this is up to you, but a general pointer is:
Coroutines make for prettier code, and are easier to write, maintain, and understand, but they’re ultimately executed by the main thread, so you might still see a performance hit depending on how much work you do per yield.
True, separate threads offer more power, but are harder to implement and result in a code which is more difficult to debug and may behave randomly. Here be dragons. So choose wisely.