Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Starting and ending threads

michael_green
Beginner
2,255 Views

In CVF I createda thread for running a DLL using CreateThread. At the end of the job I returned. No apparent problems. The same setup in IVF10 caused an access violation on return. So I read up on the subject and found that I ought to use ExitThread instead of return. Now that works OK.

But now I'm confused about whether I should use CreateThread/ExitThread or _beginthread/_endthread in order to avoid memory leaks. The documentation mentions that this depends on whether or not I'm using the C run-time library -well, am I? I don't know. I would really appreciate any theoretical input on this subject.

With many thanks in advance

Mike

0 Kudos
19 Replies
anthonyrichards
New Contributor III
2,255 Views

IMO, this should not happen, According to the Windows Help, when the thread function returns, there is an implicit call to ExitThread. Are you returning from the thread function properly? i.e. ThreadProc=yourvalue? and is yourvalue an integer*4? What Interface have you for your ThreadProc? I think it should specify By Value for the argument. like this:

interface
INTEGER(4) FUNCTION ThreadFunc(arg)
integer(4) arg
!DEC$ ATTRIBUTES VALUE :: arg
END FUNCTION
end interface

integer function ThreadFunc(idata)
!DEC$ ATTRIBUTES VALUE :: idata
integer idata
....
....

ThreadFunc=yourvalue
return
end function ThreadFunc

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,255 Views
michaelgreen:
In CVF I createda thread for running a DLL using CreateThread. At the end of the job I returned. No apparent problems. The same setup in IVF10 caused an access violation on return. So I read up on the subject and found that I ought to use ExitThread instead of return. Now that works OK.


Huh? Frankly, I've always used RETURN, and pretty much all documentation around (C/C++ oriented though) says that it's the preferred method. I've re-tested my ThreadDlg sample, which doesn't use ExitThread, and it doesn't cause an AV. There's something fishy in your code perhaps? STDCALL (CVF default) vs. CDECL (Ifort default)?

michaelgreen:
But now I'm confused about whether I should use CreateThread/ExitThread or _beginthread/_endthread in order to avoid memory leaks. The documentation mentions that this depends on whether or not I'm using the C run-time library -well, am I? I don't know. I would really appreciate any theoretical input on this subject.

I recall having an extensive discussion on the subject a couple a years ago (found it here) but I don't think we reached a definite conclusion on anything; at least Gerry Thomas had a strong disent. You're probably safe to use _beginthread(ex), but you should be quite fine with CreateThread too (note also that entry point prototypes have to be different -- stdcall vs. cdecl !). Even if there is a memory leak, it should be so small that it's negligible unless you start creating thousands of threads (or one thread repeatedly).

0 Kudos
michael_green
Beginner
2,255 Views

Hi guys,

I think I'm way out of my depth here. Here are the relevant parts of my calling routine:

interface
integer*4 function ExecRtn(rtnd)
!DEC$ IF DEFINED(_X86_)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_ExecRtn' :: ExecRtn
!DEC$ ELSE
!DEC$ ATTRIBUTES STDCALL, ALIAS : 'ExecRtn' :: ExecRtn
!DEC$ ENDIF
type t_rtn_designparams
character*7dbase
integer*4hProgress,hDlg,ghWndMain,IDC_task
character*200fmis$dbase,fmis$work,fmis$exec,dgnfile,descfile
character*64master,theme
character*3ltype
character*2polstart,polend,tlevel,extcode
logicalagd84,OK
end type t_rtn_designparams

type (t_rtn_designparams)rtnd

end function
end interface

integer*4

handles(1),idThread

!Do some things ...

rtnd = t_rtn_designparams(dbase,hProgress,hDlg,ghWndMain,IDC_task, &
fmis$dbase,fmis$work,fmis$exec,dgnfile,descfile,master,theme,ltype,polstart, &
polend,tlevel,extcode,agd84,OK)
handles(1) = CreateThread(NULL,0,loc(ExecRtn),loc(rtnd),0,loc(idThread))
iret = CloseHandle(handles(1))

!Tidy up

And in the thread DLL I've got:

subroutine designtofmis(db,hProgress,hDlg,IDC_task,hWndMain,fmisdbase,fmiswork,fmisexec, &
dgnfile,theme,ltype,cpstart,cpend,ctlevel,descfile,extcode,newtheme,agd84,OK)

!DEC$ ATTRIBUTES DLLEXPORT::designtofmis

!Do job ...

return

!call ExitThread(0)

end

With Return I get an access violation, with ExitThread, no problem.

Thanks again for any insight.

Mike

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,255 Views
Everything looks OK on the surface (I assume you call designtofmis from ExecRtn?). I'd like you to try the following, one by one, though:
1) Not calling anything from ExecRtn,
2) Calling a simple "hello, world" routine from the .exe,
3) Calling a simple "hello, world" routine from the .dll,
4) Leave the code as is now, but call ExecRtn directly rather than via CreateThread.

Here, ExitThread abruptly ends the thread, but RETURN would return control from designtofmis to ExecRtn first, and nothing should have happened anyway at that point, thread or no thread. If there's a problem related with Ifort handling of threads, it would show up upon return from ExecRtn, not upon return from designtofmis... at least I think so.
0 Kudos
Steven_L_Intel1
Employee
2,255 Views
What happens if you add REFERENCE to the ATTRIBUTES list for the routine? STDCALL implies VALUE and derived type arguments are passed/accepted by value if you simply say STDCALL.
0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,255 Views
I think you nailed it Steve. The epilog code after RETURN from ExecRtn will wipe out a couple of kilobytes (SIZEOF(rtnd)) off the stack... kaboom.

Though, as I complained before, the Intel should have been bold and at least remove default VALUE for TYPEs. (And the entire !DEC$ATTRIBUTES semantics is so arcane* that it was well worth ditching away when introducing DVF5, but I sympathize with decision to go with FPS compatibility).

--
*) for example, REFERENCE is default for arguments, unless a C or STDCALL is specified. In that case, "unqualified" REFERENCE means that it's still the default. However, it doesn't affect string arguments: when applied to specific string argument, it also means "don't pass hidden length"... unless you don't have C or STDCALL attribute, in which case it does nothing. Go figure.
0 Kudos
Steven_L_Intel1
Employee
2,255 Views
I sympathize, but I think we'll just have to live with the warts on this one.
0 Kudos
Steven_L_Intel1
Employee
2,255 Views
FWIW, this is the same behavior as CVF, but with CVF one didn't need to add the STDCALL attribute so I can see why it might appear as a new issue.
0 Kudos
jimdempseyatthecove
Honored Contributor III
2,255 Views

Mike,

I believe you may have a coding error. After CreateThread you are issuing CloseHandle on the thread handle prior to the thread termination. MSDN has"

"Closing a thread handle does not terminate the associated thread. To remove a thread object, you must terminate the thread, then close all handles to the thread."

This statement seems to imply a sequence where the CloseHandle must follow termination of the thread.

Consider inserting

ThreadRtn = WaitForSingleObject(handles(1)),INFINITE)

between the CreateThread and CloseHandle. If you do not wait then a) you are in violation of the implicit sequencing as stated above, but more importantly b) the code that created the thread holds the data objects used by the DLL. Your code following !Tidy up is likely going to destroy (deallocate, or pop from stack) data in use by the DLL.

Also, you should test to see if the CreateThread failed and take appropriate action.

Jim Dempsey

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,255 Views
JimDempseyAtTheCove:

I believe you may have a coding error. After CreateThread you are issuing CloseHandle on the thread handle prior to the thread termination. MSDN has"

"Closing a thread handle does not terminate the associated thread. To remove a thread object, you must terminate the thread, then close all handles to the thread."

This statement seems to imply a sequence where the CloseHandle must follow termination of the thread.

It does not, actually. You may -- and even should, even if only for the purpose of clarity -- CloseHandle as soon as you don't need it anymore (the handle, not the object it refers to), regardless if the object is still alive. A handle is just something you're given by the system to "name" the object. If you don't need to call it by name anymore, you don't need to use it and should ditch it. Thus, Mike's code structure is correct.

WaitForSingleObject kind of defeats the purpose of having a thread (in the case at hand), doesn't it? Of course, he may not create a race condition after !Tidy up (e.g. deallocate arrays used by the thread).
0 Kudos
michael_green
Beginner
2,255 Views

Hi Guys,

Adding REFERENCE to the attributes list didn't make any difference. But I have followed up on Jugoslav's suggested tests and have learnt a few things.

First of all, the problem is generic - it happens in several similarly structured, but unrelated DLLs called in separate threads.

I modified one of the DLLs to contain the equivalent of a hello world program as follows:

subroutine shapetofmis(db,hProgress,hDlg,IDC_task,hWndMain,fmisdbase,fmiswork,fmisexec,shapefile, &
oldtheme,newtheme,ltype,codecol,descfile,extcode,agd84,OK)

!DEC$ ATTRIBUTES REFERENCE, DLLEXPORT ::shapetofmis
...

bytedummy(512)

...

open(1,file=shapefile,status='old',form='unformatted',recordtype='stream', &
iostat=ios,err=1000)

eof = .false.
read(1,iostat=ios)(dummy(i),i=1,512)
nreads = 1

if(ios==-1)then
eof = .true.
else if(ios/=0)then
goto 1000
end if

!do while(.not.eof)
do while(nreads<1252)
read(1,iostat=ios)(dummy(i),i=1,512)
if(ios==-1)then
eof = .true.
else if(ios/=0)then
goto 1000
end if
nreads = nreads + 1
end do
close(1)

!write(junk,'(I7)')nreads
iret = MessageBox(0,'Successful test. nreads = '//junk//char(0),'Shape to FMIS'C,MB_OK)

return
!call ExitThread(0)

I have commented out two crucial lines before the return statement, and the above works as expected. The file requires 1253 reads to get to eof. If I reinstate the do while(.not.eof) (and comment out the do while (nreads<12532)) I get an access violation when the thread exits. Alternatively I can leave that as it is and reinstate the write(junk...) statement. That also causes an AV. All problems disappear if I use ExitThread.

The test program, above, sits inside the original DLL, and all the DLL's variables are still declared and there is much dead code following the return. The program includes some large allocatable arrays, although at the point where the test occurs, they have not been allocated.

With many thanks to all

Mike

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,255 Views

Jugoslav,

I know you can CloseHandleon the thread while it is active (now). However, this is contrary to the documentation (at least in the section describing CloseHandle). So one might consider this an "undocumented feature" (read matches to burn fingers with later).

As for WaitForSingleObject.

Generally you insert DoMoreWork(); in front of the WaitForSingleObject.

A thread spawned by a process, and unless indicated otherwise shares the same virtual address and i/o handles as the process in which it is spawned. Furthermore, in the sample code given, it is unclear if any of the context information used in the setup are stack local to the thread spawning function (or up one level, two levels, etc... all the way to main) as well as ifthe tidy up code manages to exit the thread spawing function and exit one level, two levels, etc... and even exit the PROGRAM code section back to the CRT startup routine, where it cleans up the heap,and manages to get there _prior_ to the DLL thread finishing up. Even if the context information is used once by the DLL you must be certain that it is done being used by the DLL startup prior to destroying it. It is relatively easy to insert a DLLsetupDone variable that is initialized to FALSE and is set to TRUE at the point in the DLL where you no longer will access anything passed for initialization. When this become TRUE then you can go ahead and tidy up.

So WaitForSingleObject at the appropriate point in the code is important.

If your intention is to launch the DLL thread and let it run independently then run it as a process it its own address space.

Jim Dempsey

0 Kudos
michael_green
Beginner
2,255 Views

I forgot to say in my earlier post, I inserted the line:

iret = WaitForSingleObject(handles(1),INFINITE)

between the call to CreateThread and CloseHandle, but it made no difference.

thanks again

Mike

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,255 Views
JimDempseyAtTheCove:
I know you can CloseHandleon the thread while it is active (now). However, this is contrary to the documentation (at least in the section describing CloseHandle). So one might consider this an "undocumented feature" (read matches to burn fingers with later).


No it's not. Quote from CloseHandle documentation: "Closing a thread handle does not terminate the associated thread or remove the thread object." Period. The sequel describes, in not so fortunate wording, what you need to do if you want to terminate the thread. I said that the wording is unfortunate, as it does not cover the situation when thread dies by natural death (return/ExitThread); but MSDN is not perfect.

JimDempseyAtTheCove:
So WaitForSingleObject at the appropriate point in the code is important.

If your intention is to launch the DLL thread and let it run independently then run it as a process it its own address space.


I think you're reading too much in two words of a comment "tidy up" . True, there might be the problem where variables in use by the dll are messed up with; but we don't have enough data to conclude that. Yes, WaitFor... is often used in threading code, but there are also legitimate situations where one launches a thread and forgets about it (e.g. do you care when a thread writing output data to a slow medium such is finished, provided it has its own locking mechanism?).

Mike, I'd appreciate if you could assemble a small example which exhibits the problem and attach a zip file.
0 Kudos
jimdempseyatthecove
Honored Contributor III
2,255 Views

Mike,

Good. (assuming the WaitForSingleObject waited instead of reporting invalid handle) then this eliminates anything in your tidy up code and leaves you to examine your initialization (what goes in to CreatThread) and the code in the DLL itself.

As Jugoslav pointed out, a simple failing sample application would help one of us to point out the problem you are encountering. Or, in preparing the simple failing application you might discover for yourself what the problem is.

Jim Dempsey.

0 Kudos
michael_green
Beginner
2,255 Views

Hi Jim & Jugoslav,

I have created two small projects - one is a simple Windows program (Testbed1), the other is a DLL (calc_pi)- that demonstrate the problem. I guess you only need the two .f90 files, but I've gone for overkill and placed the lot in one zip file. The two resource files belong to the the Testbed1 project. The rest should be obvious.

Many thanks again, I really appreciate your very interesting input.

Mike

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,255 Views
Mike, I played a bit with your sample, and (at least according to my limited testing) concluded that you get the crash ONLY if:
  • You link both projects with (Static Debug) Multithreaded run-time library (i.e. both the exe and the dll get a separate static copy), AND
  • Load the dll dynamically, AND
  • Call FreeLibrary at the end of ExecRtnP
It crashes following RETURN statement in ExecRtnP, seemingly within a call to a Kernel32.dll routine.

While that combination is not forbidden as far as I can tell, I can imagine that there is a call to a non-existing run-time library routine or something like that. I'm not ready to call it a compiler/RTL bug, rather than perhaps an unexpected/unfortunate combination of circumstances. Perhaps Steve or one of developers could provide some more insight.
0 Kudos
Steven_L_Intel1
Employee
2,255 Views
I could believe such a problem might occur with two copies of the RTL - I usually strongly recommend against such a practice. I don't have time right now to look at it in detail.
0 Kudos
michael_green
Beginner
2,255 Views

Hi Guys,

I changed the RTL settings as per Jugoslav's comments and all is now well. But the odd thing is, I never chose the former settings, I had never even looked at them before, they are what came over when I converted from CVF to IVF. I presumed all along that I had a coding error to which IVF was sensitive whereas CVF was not.

Many thanks to all,

Mike

0 Kudos
Reply