Since you are using your C # library from C # through the C ++ / CLI layer, it looks like your link relationships look like this:
(C # application) - dynamic β (C ++ / CLI DLL) - static β (C library)
Background:
Since you stated that you have no way to modify or otherwise commit your C library, you really have no way to solve the thread safety problem without duplicating the C library in memory anyway, which will allow you to effectively duplicate global data that / certainly causes thread conflicts.
No matter how you look at it, your only way to solve this problem is to duplicate the DLL (your current approach) will require you to know how many duplicates you need in advance, and each of these DLLs will be slightly different from the exported ones (for example, each the class or namespace name will need another C ++ / CLI class class). As you stated, this is really rude (and I will add it is difficult to manage and impossible to scale).
Decision:
I think your best and cleaner option is with EXE duplication. To do this, you insert a new EXE in front of your C ++ / CLI DLL and dynamically link it using the IPC mechanism of your choice ( IPC Mechanisms in C # - Usage and Best Practices ). Lets call this exe your exe service. If you create an instance of the EXE service for each thread in the main C # application, then each EXE service will load its own copy of the C ++ / CLI DLL and, therefore, its own version of the C library. This means that each thread works with a copy of the library C through the exe service.
Here's what your link relationships look like:
(C# application)--IPC-->(C
In the physical conditions of the file, it will look like this:
MainApp.exe--IPC-->ServiceApp.exe--dynamic-->CppCliWrapper.dll
To optimize this solution, you should try to avoid creating / deleting heavy threads, as this is related to creating / deleting the EXE service. For example, you can pre-allocate X threads at startup and use these threads throughout the entire application life cycle. You might also consider performing operations to optimize IPC costs. It all depends on the needs and details of your application.
To do this even more, you can potentially scale this solution across all computers if you choose an IPC mechanism that works over TCP / IP. Then you can process your operations on as many machines as you want. This is how many companies turn DLLs into horizontally scalable services. It's not as simple as it sounds, but food, though, if you need to scale this thing.