Repository: opencv
Issue: Crash in parallelized algorithms in multithreaded application
Labels: bug,platform: win32,needs investigation

##### System information (version)

- OpenCV => 3.4.1, but code is the same in current version
- Operating System / Platform => Win11
- Compiler => Microsoft v141 (VS2017)

##### Detailed description

In an application which is parallelized from application side, opencv algorithms with built-in parallization, the use of the Concurrency::Scheduler may produce a crash. The crash happens in `parallel.cpp` in line `pplScheduler->Attach();`
```
        if(!pplScheduler || pplScheduler->Id() == Concurrency::CurrentScheduler::Id())
        {
            Concurrency::parallel_for(stripeRange.start, stripeRange.end, pbody);
        }
        else
        {
            pplScheduler->Attach();
            Concurrency::parallel_for(stripeRange.start, stripeRange.end, pbody);
            Concurrency::CurrentScheduler::Detach();
        }
```

CallStack:
```
 	KernelBase.dll!RaiseException()	Unknown
 	vcruntime140.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 136	C++
 	concrt140.dll!Concurrency::details::SchedulerBase::Reference() Line 1395	C++
 	[Inline Frame] concrt140.dll!Concurrency::details::SchedulerBase::ReferenceForAttach() Line 2639	C++
 	concrt140.dll!Concurrency::details::SchedulerBase::AttachExternalContext(bool explicitAttach) Line 1555	C++
 	concrt140.dll!Concurrency::details::SchedulerBase::Attach() Line 1477	C++
>	ImgProcessingToolsTest.exe!parallel_for_impl(const cv::Range & range, const cv::ParallelLoopBody & body, double nstripes) Line 475	C++
 	ImgProcessingToolsTest.exe!cv::parallel_for_(const cv::Range & range, const cv::ParallelLoopBody & body, double nstripes) Line 402	C++
 	ImgProcessingToolsTest.exe!cv::connectedcomponents::LabelingGranaParallel<int,unsigned char,cv::connectedcomponents::CCStatsOp>::operator()(const cv::Mat & img, cv::Mat & imgLabels, int connectivity, cv::connectedcomponents::CCStatsOp & sop) Line 2577	C++
 	ImgProcessingToolsTest.exe!cv::connectedComponents_sub1<cv::connectedcomponents::CCStatsOp>(const cv::Mat & I, cv::Mat & L, int connectivity, int ccltype, cv::connectedcomponents::CCStatsOp & sop) Line 3983	C++
 	ImgProcessingToolsTest.exe!cv::connectedComponentsWithStats(const cv::_InputArray & img_, const cv::_OutputArray & _labels, const cv::_OutputArray & statsv, const cv::_OutputArray & centroids, int connectivity, int ltype, int ccltype) Line 4037	C++
 	ImgProcessingToolsTest.exe!cv::connectedComponentsWithStats(const cv::_InputArray & img_, const cv::_OutputArray & _labels, const cv::_OutputArray & statsv, const cv::_OutputArray & centroids, int connectivity, int ltype) Line 4020	C++
```

The `pplScheduler` is a global variable which is not protected with a mutex. It is initialized within method cv::setNumThreads:
```
void cv::setNumThreads( int threads_ )
{
    (void)threads_;
#ifdef CV_PARALLEL_FRAMEWORK
    int threads = (threads_ < 0) ? defaultNumberOfThreads() : (unsigned)threads_;
    numThreads = threads;
#endif
...
    if (threads <= 0)
    {
        pplScheduler = 0;
    }
    else if (threads == 1)
    {
        // Concurrency always uses >=2 threads, so we just disable it if 1 thread is requested
        numThreads = 0;
    }
    else if (pplScheduler == 0 || 1 + pplScheduler->GetNumberOfVirtualProcessors() != (unsigned int)threads)
    {
        pplScheduler = Concurrency::Scheduler::Create(Concurrency::SchedulerPolicy(2,
                       Concurrency::MinConcurrency, threads-1,
                       Concurrency::MaxConcurrency, threads-1));
    }

```


##### Steps to reproduce

Starting an algorithm with built-in parallelization from multiple threads. You may have to run the program multiple times until you get the crash. 

```
int main()
{
	std::vector<std::thread> threads;
	std::shared_mutex m;
	m.lock();
	for (size_t i = 0; i < std::thread::hardware_concurrency() ; ++i)
	{
		threads.push_back(std::thread(
				[i, &m]()
				{
					// wait until all theads are running
					m.lock_shared();
					cv::Mat start(cv::Size(128, 128), CV_8UC1);
					start = 0;
					cv::Mat labelImage(start.size(), CV_32S);
					cv::Mat labelStats;		
					cv::Mat labelCentroids;	 
					int nLabels = cv::connectedComponentsWithStats(start, labelImage, labelStats, labelCentroids, 8);
					m.unlock_shared();
				}));
	}
	m.unlock();
	for (auto&&t : threads)
	{
		t.join();
	}
	return 0;
}
```


Workaround: enforcing the initialization of the internal scheduler before running the algorithm in parallel seems to solve the issue:
```
 	const int nThreads = cv::getNumberOfCPUs();
  	cv::setNumThreads(nThreads);
```

But as soon as any other algorithm is using a different thread count, the crash may happen again.