I have been working on a project where a number of threads need to access a global array. I made a test script and got some very interesting results. I wanted to share incase it might be beneficial to some.
Briefly, I found out,
0. locks work as intended if they are used on both threads accessing the variable.
- Without locks, if two threads work on same array of structs simultaneosly, there is ~1% chance of race conditions occuring. If you can tolerate this, it saves you worrying about locks.
- locking each variable just before write/read, costs alot, by almost 20 times of not using locks.
- If you lock a variable at the very beginning, the other thread will have to wait and thus it wont be truely simultenous.
- Most interestingly, if you lock on only one thread, but only before writing/reading, it saves a bunch of race conditions from happenning (dropping to only ~0.02%) with costing only about 5 times as much (instead of 20)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
using System.Threading;
using System.Linq;
public class multithread_testing2 : MonoBehaviour
{
static bool finished_sideThread = false;
static bool finished_mainThread = false;
string timeElapsed_SideThread;
string timeElapsed_MainThread;
Vector3[] v3_array = new Vector3[1000];
Vector3[] v3_reference_array = new Vector3[1000];
private void Update()
{
if (finished_sideThread && finished_mainThread)
{
int raceConditions = 0;
finished_sideThread = false;
// TODO: Check that arr1.Length and arr2.Length are the same
for (int i = 0; i < v3_array.Length; i++)
{
if (v3_array[i] != v3_reference_array[i])
{
raceConditions += (int)(v3_reference_array[i].x - v3_array[i].x);
UnityEngine.Debug.Log("Difference at element " + i.ToString() + ", " + v3_array[i].ToString() + " != " + v3_reference_array[i].ToString());
}
}
print("total number race conditions occured:" + raceConditions.ToString() + "\nTime elapsed on side thread:" + timeElapsed_SideThread + "\nTime elapsed on main thread:" + timeElapsed_MainThread);
}
}
void Start()
{
for (int i = 0; i < v3_array.Length; i++) //initialize the variables on main thread.
{
v3_array[i] = new Vector3(0f, 0f, 0f);
v3_reference_array[i] = new Vector3(0f, 0f, 0f);
}
new Thread(WorkWork).Start(); //Start side thread. Race conditions occur here.
#region MainThread WorkWork
Stopwatch mt_timer = new Stopwatch();
int p=0;
mt_timer.Start(); //Start timer before lock
//lock (v3_array)//comment to exclude lock (1ST LOCK)
{
while (p < 100000)
{
p++;
//lock (v3_array)//comment to exclude lock (2ND LOCK)
{
for (int i = 0; i < v3_array.Length; i++)
{
//lock (v3_array)//comment to exclude lock (3ND LOCK)
v3_array[i].x += 1f;
v3_reference_array[i].x += 2f;
}
}
}
}
mt_timer.Stop();
timeElapsed_MainThread = mt_timer.Elapsed.ToString();
finished_mainThread = true;
#endregion
//new Thread(WorkWork).Start(); //Start thread //No race condition occurs if after main
}
void WorkWork() //continuesly changes the static variable
{
Stopwatch timer = new Stopwatch();
int p = 0;
timer.Start(); //Start timer before lock
//lock (v3_array)//comment to exclude lock (4RD LOCK)
{
if (v3_reference_array[0].x == 200000) print("MainThread finished before SideThread started!");
while (p < 100000)
{
//lock (v3_array)//comment to exclude lock (5TH LOCK)
{
p++;
for (int i = 0; i < v3_array.Length; i++)
{
lock (v3_array)//comment to exclude lock (6TH LOCK)
v3_array[i].x += 1f;
}
}
}
}
timer.Stop();
timeElapsed_SideThread = timer.Elapsed.ToString();
finished_sideThread = true;
}
}
RESULTS:
//without any lock, using non-static variables
// total number race conditions occured: 969047 in 100000000 =~1%
//Time elapsed on side thread: 00:00:00.3689510
//Time elapsed on main thread: 00:00:00.4870519
// total number race conditions occured: 792564
//Time elapsed on side thread: 00:00:00.3165800
//Time elapsed on main thread: 00:00:00.4532916
//without locks using static variables:
// total number race conditions occured: 978096 in 100000000 =~1%
//Time elapsed on side thread: 00:00:00.3232456
//Time elapsed on main thread: 00:00:00.4566593
// total number race conditions occured: 961748
//Time elapsed on side thread: 00:00:00.3216992
//Time elapsed on main thread: 00:00:00.4596025
//with locks 3,6 using non-static variables:
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:13.4967897
//Time elapsed on main thread: 00:00:13.7488787
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:14.9234585
//Time elapsed on main thread: 00:00:15.2244580
//with lock 1 and 4 using non-static variables: “MainThread finished before SideThread started!”
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.6029872
//Time elapsed on main thread: 00:00:00.3307784
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.5803853
//Time elapsed on main thread: 00:00:00.3024417
//with locks 3,6 using static variables:
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:16.5628217
//Time elapsed on main thread: 00:00:16.6551825
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:14.0155613
//Time elapsed on main thread: 00:00:14.1519171
//with lock 1 and 4 using static variables: “MainThread finished before SideThread started!”
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.5682462
//Time elapsed on main thread: 00:00:00.3209186
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.5819123
//Time elapsed on main thread: 00:00:00.3230487
//with locks 2,5 using static variables:
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.6816896
//Time elapsed on main thread: 00:00:00.6584224
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.4032680
//Time elapsed on main thread: 00:00:00.6552052
//with locks 2,5 using non-static variables:
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.6975935
//Time elapsed on main thread: 00:00:00.6521430
// total number race conditions occured: 0
//Time elapsed on side thread: 00:00:00.6321385
//Time elapsed on main thread: 00:00:00.3994793
//using only 4TH lock:
// total number race conditions occured: 522735 in 100000000 =~0.5%
//Time elapsed on side thread: 00:00:00.4555784
//Time elapsed on main thread: 00:00:00.5212350
// total number race conditions occured: 519063
//Time elapsed on side thread: 00:00:00.4844576
//Time elapsed on main thread: 00:00:00.5330215
//using only 5TH lock:
// total number race conditions occured: 642547 in 100000000 =~0.5%
//Time elapsed on side thread: 00:00:00.4665403
//Time elapsed on main thread: 00:00:00.5322832
// total number race conditions occured: 396752
//Time elapsed on side thread: 00:00:00.4863987
//Time elapsed on main thread: 00:00:00.5260168
//using only 6TH lock:
// total number race conditions occured: 19731 in 100000000 =~0.02%
//Time elapsed on side thread: 00:00:02.6481066
//Time elapsed on main thread: 00:00:00.5695836
// total number race conditions occured: 18969
//Time elapsed on side thread: 00:00:02.6499795
//Time elapsed on main thread: 00:00:00.5332299