Thread: Thread counting problem

  1. #1
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733

    Thread counting problem

    I know on linux it's possible to count the thread via /proc/self/status or counting the directories in /proc/self/task (at least I think that was the directory), the problem is that involves opening a file descriptor, on modern systems that's not a problem but since the library is supposed to be compatible with older systems as well I have to account for the file descriptor limit which means a possible failure to count the threads via that method, is there any possible way to count the number of threads in the process without opening an fd? Haven't looked into the windows method of doing this but I'd wager CreateToolHelp32Snapshot or whatever it was called can help me out there if need be.

    The reason I even thought to attempt this is related to attempting to guarantee mutices are only deleted when no other threads can try to lock it during deletion (since I had to use an allocated pthread_mutex_t to ensure the exposed typedef had a simple initialiser that aligned with window's HANDLE), and the only way I can think of for that is to just refuse to do so while there is more than 1 thread (the main thread) that can potentially try to lock it at the same time the mutex becomes invalid.

  2. #2
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Judging by the number of views & the lack of responses I'm gonna go ahead and assume there is no other method, that's irritating, I'll have to see if /proc/self/status is always open in the process, if it is then maybe there's a way to grab it's fd for reading

  3. #3
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    I came up with a solution to my mutex closing problem. Instead of maintaining random pointers and file descriptors I'll instead use my main mutex & file to store pseudo mutices, granted it *MAY* be slower than a normal mutex (hopefully I can resolve that with memory mapping) but I can safely block new locks and wait on old locks to be released without needing to count the threads that potentially need to know about the closure, here's my initial globals for a slightly clearer idea of what I'm doing:

    Code:
    static pawjd	tid = -1;
    static pawd		mutex_c = 0;
    #ifdef PAW_API_MSW
    /* temporary file handle for lifespan of paw's initialised state */
    static HANDLE	_mutices = NULL;
    static HANDLE	*mutices = NULL;
    #else
    /* temporary file descriptor for lifespan of paw's initialised state */
    static pawd		_mutices = -1;
    static pawd		*mutices = NULL;
    static pthread_mutex_t	_mutex;
    #endif
    
    typedef struct _mutex_t
    {
    	bool taken;
    	pawjd tid;
    	paws name;
    	pawd locks;
    } mutex_t;
    Haven't finished the code yet but I can see this working better and avoiding the possibility of hitting the file descriptor limit due to too many mutices, also avoids colliding with external mutex APIs

  4. #4
    Registered User rstanley's Avatar
    Join Date
    Jun 2014
    Location
    New York, NY
    Posts
    1,112
    Quote Originally Posted by awsdert View Post
    Judging by the number of views & the lack of responses I'm gonna go ahead and assume there is no other method, that's irritating, I'll have to see if /proc/self/status is always open in the process, if it is then maybe there's a way to grab it's fd for reading
    Tried to send you a P/M but you don't allow them.

  5. #5
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Quote Originally Posted by rstanley View Post
    Tried to send you a P/M but you don't allow them.
    That's because baring personal information like address etc which only the admin would have any kind of access to, there's no real reason for me to need PMs since at most the only thing that might be put in a PM is just someone having an issue with what I said, since that's not worth hiding I don't bother allowing it to be hidden, if they can't say it publicly then they're already in the wrong

  6. #6
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    I guess you have kind of a design issue if you really have to count threads. Usually you would join them. Once all are joined you can release the resources used to synchronize things between the threads.
    Have a look at pthread_join(), and (for Windows threads) WaitForSingleObject() or WaitForMultipleObjects().

  7. #7
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Quote Originally Posted by aGerman View Post
    I guess you have kind of a design issue if you really have to count threads. Usually you would join them. Once all are joined you can release the resources used to synchronize things between the threads.
    Have a look at pthread_join(), and (for Windows threads) WaitForSingleObject() or WaitForMultipleObjects().
    More like because it's a library function I'm making it has to assume the dev might trying to use it in odd ways, most ways can lead to an understandable result that leads the dev back to how they used the function incorrectly but a data race is not so easy to pick out in a crash caused by a double closure of a mutex or an attempt to lock it while it is being closed, it's easy to prevent double creation by only allowing the main thread to create mutices but not so easy to prevent double closure or simultaneous locks since no library can prevent the situation of a newly spawned library trying to lock the mutex so they can tell it about itself, what if the mutex concludes there are no more threads to protect against before deleting and starts doing so only for a newly spawned thread to then try to lock the mutex at the same time it reaches that conclusion and locks the mutex at the same time the deleting thread reaches the closure call? The system may or may not handle it correctly and there's no easy way to declare a mutex invalid if the threads are using copies of the pointers given, even when I tried double pointers (**) I still found data races, no the only solution to stop a data race is to refuse to delete the mutex while there is more than the main thread running, that would of course be ridiculous for every possible mutex that may be spawned so I'm now going with 1 internal mutex which can have it's invalid state declared before it's actually made invalid (via the use of pointers) and pseudo mutices in a buffer (only just realised I didn't need the file for my pseudo mutices, good thing I slacked off and haven't written any code for it yet) which get updated after the internal lock is taken. It might be slower but that's the price paid for consistency both in the exposed api and the local APIs available, the goal of my library is not to outright replace pthreads etc as optional libraries to include but reduce the need to map them in in the first place, it's entirely possible that the dev will still need some specific functions from each api when running on each system, the use of my library would then not be possible if I were to design it to need access to information that those APIs do not provide or create custom implementations of things that could then collide with their implementations. If I can map it into my library in a cross-platform way it will be mapped, if I can't then it's up to the dev to decide if they need that functionality on that platform, at which point they can request the handle/descriptor etc from my library and use it as needed.

  8. #8
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    I don't know what library you're talking about and I don't know what influence you have in terms of the design of this library. However, I've literally never seen a code that has to count still running threads. If you're worried about crossplatform compatibility use the C11 lib referenced in <thread.h> or a library that alraedy wraps pthreads and Windows threads into a common interface. Also, what about reference counting of your mutex using an atomic type?

  9. #9
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Quote Originally Posted by aGerman View Post
    I don't know what library you're talking about and I don't know what influence you have in terms of the design of this library. However, I've literally never seen a code that has to count still running threads. If you're worried about crossplatform compatibility use the C11 lib referenced in <thread.h> or a library that alraedy wraps pthreads and Windows threads into a common interface. Also, what about reference counting of your mutex using an atomic type?
    I originally looked into the atomic thing at the start but that requires C11, I'm trying to make my library compilable under
    Code:
    -std=c89 -ansi -pedantic
    which unfortunately rules out atomics, one fallback could involve the signal handler but that then introduces incompatibility to some extent which is contrary to my goal of allowing the dev to pick & mix what they use of the library and what the system provides. As far as influence goes, I'm the only developer for it (for now, I'll add in interested parties once I'm satisfied with the 1st version of the API, the 2nd version onwards is for performance improvements and expansion mainly) so I have full control of the library, the external libraries like say wxwidgets, unity, unreal etc are not something I can greatly influence (if at all) so my library should play nice with them instead which is why I go for the assumption that there may have been threads launched outside of my libraries code that still have access to the mutex, for example opengl's debug callback, it could access the mutex from a thread launched by opengl but the thread deleting the mutex could've been launched by my library or vise versa or both threads could've been launched outside of my library, I cannot assume my library has full control over all the number of threads and the records thereof, I have to assume the opposite, that it has no control over both

  10. #10
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    Don't handle mutexes that you don't own, don't handle threads that you don't own. Return to the caller if you're done in your library code. Ensure you cleaned up the resources you own before you return to the caller. But never clean up resources where you are not the owner.

  11. #11
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Quote Originally Posted by aGerman View Post
    Don't handle mutexes that you don't own, don't handle threads that you don't own. Return to the caller if you're done in your library code. Ensure you cleaned up the resources you own before you return to the caller. But never clean up resources where you are not the owner.
    But that's the thing, it's impossible to make sure the dev doesn't do exactly that, when you make a library publicly available like I am, you have to assume the worst because there WILL be those corner cases you don't want happening in the 1st place, your only choice then is to do what you can to catch those instances and deal with it if you can or segfault if you can't, either way you have to deal with it to help the dev catch their error.

  12. #12
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Haven't gotten round to compiling this since I've got cleanup to do in the original file it existed (linker will complain of redefined symbols otherwise) however I'm curious to know if anyone can see any potential issues (all the paw* types just map to printf equivs like %d, %jd etc, just ignore the paw prefix to understand which it is).

    Code:
    #include "rawpaw.h"
    
    static pawjd	tid = -1;
    static pawd		locks = 0;
    static pawd		mutexcount = 0;
    #define MUTICES_PER_ALLOC 32
    #ifdef PAW_API_MSW
    typedef HANDLE sysmtx_t;
    #else
    typedef pthread_mutex_t sysmtx_t;
    #endif
    
    static sysmtx_t	_shared_mutex, *shared_mutex = NULL;
    
    typedef struct _mutex_t
    {
    	bool taken;
    	pawjd tid;
    	paws name;
    	pawd locks;
    } mutex_t;
    
    mutex_t *_mutices = NULL;
    mutex_t *mutices = NULL;
    
    #ifdef PAW_API_MSW
    static void rawpaw__free_mutex() { ReleaseMutex( _shared_mutex ); }
    static void rawpaw__lock_mutex()
    	{ WaitForSingleObject( _shared_mutex, INFINITE ); }
    static void rawpaw__term_mutex()
    	{ CloseHandle( _shared_mutex ); _shared_mutex = NULL; }
    static sysmtx_t* rawpaw__init_mutex()
    {
    	_shared_mutex = CreateMutex(NULL,FALSE,NULL);
    	return _shared_mutex ? &_shared_mutex : NULL;
    }
    #else
    
    static void rawpaw__free_mutex() { pthread_mutex_unlock( &_shared_mutex ); }
    static void	rawpaw__lock_mutex() { pthread_mutex_lock( &_shared_mutex ); }
    static void rawpaw__shut_mutex() { pthread_mutex_destroy( &_shared_mutex ); }
    static sysmtx_t* rawpaw__init_mutex()
    {
    	sysmtx_t *mtx = &_shared_mutex;
    	return (pthread_mutex_create( mtx, NULL ) == 0) ? mtx : NULL;
    }
    #endif
    
    RAWPAW_API void rawpaw_free_mutex() { rawpaw__free_mutex(); }
    RAWPAW_API pawd rawpaw_lock_mutex()
    {
    	rawpaw__lock_mutex();
    	if ( !mutices )
    	{
    		rawpaw__free_mutex();
    		return EADDRNOTAVAIL;
    	}
    	return 0;
    }
    void rawpaw_shut_mutex()
    {
    	pawd i = 0, n = 0;
    	/* Signal that the mutices are going to be removed */
    	rawpaw__lock_mutex();
    	mutices = NULL;
    	rawpaw__free_mutex();
    	/* Wait until all threads have released their mutices */
    	while ( n < mutexcount )
    	{
    		paw_yield();
    		rawpaw__lock_mutex();
    		for ( i = 0, n = 0; i < mutexcount; n += _mutices[i++].taken );
    		rawpaw__free_mutex();
    	}
    	/* No more data races possible */
    	mutexcount = 0;
    	shared_mutex = NULL;
    	_mutices = pawdel( _mutices );
    	rawpaw__shut_mutex( _shared_mutex );
    }
    pawd rawpaw_init_mutex()
    {
    	if ( shared_mutex )
    		return EALREADY;
    	if ( pawgetid() != pawgetmainid() )
    		return EACCES;
    	errno = 0;
    	_mutices = pawgetUD( sizeof(mutex_t) * MUTICES_PER_ALLOC );
    	if ( !_mutices )
    		return errno;
    	errno = 0;
    	shared_mutex = rawpaw__init_mutex();
    	if ( !shared_mutex )
    	{
    		pawd err = errno;
    		pawdel(_mutices);
    		return err;
    	}
    	mutices = _mutices;
    	mutexcount = MUTICES_PER_ALLOC;
    	return 0;
    }
    
    RAWPAW_API pawmtx pawmtx_create()
    {
    	pawd err = rawpaw_lock_mutex(), i = 0;
    	pawzd old = 0, nxt = 0;
    	if ( err )
    	{
    		errno = err;
    		return -1;
    	}
    	for ( i = 0; i < mutexcount; ++i )
    	{
    		if ( !(mutices[i].taken) )
    		{
    			mutices[i].taken = 1;
    			rawpaw_free_mutex();
    			return i;
    		}
    	}
    	old = sizeof(mutex_t) * mutexcount;
    	nxt = sizeof(mutex_t) * (mutexcount + MUTICES_PER_ALLOC);
    	mutices = pawget( _mutices, nxt );
    	if ( !mutices )
    	{
    		rawpaw_free_mutex();
    		mutices = _mutices;
    		return -1;
    	}
    	_mutices = mutices;
    	mutices[i].tid = -1;
    	mutices[i].name = "";
    	mutices[i].locks = 0;
    	mutices[i].taken = 1;
    	rawpaw_free_mutex();
    	return i;
    }
    RAWPAW_API void pawmtx_delete( pawmtx mtx )
    {
    	if ( mtx < 0 )
    		return;
    	if ( !shared_mutex )
    	{
    		/* Mutex was already released allowing shared mutex to be released,
    		 * in other words you've called this function on the same mutex 2 or
    		 * more times, that should NEVER happen. */
    		pawRaise(PAW_E_SIGNAL_SEGVEC);
    		exit(EXIT_FAILURE);
    		return;
    	}
    	rawpaw__lock_mutex();
    	if ( mtx < mutexcount )
    	{
    		mutex_t *mutex = _mutices + mtx;
    		if ( mutex->tid == pawgetid() || mutex->tid < 0 )
    		{
    			mutex->name = NULL;
    			mutex->locks = 0;
    			mutex->taken = 0;
    			mutex->tid = -1;
    		}
    	}
    	rawpaw__free_mutex();
    }
    RAWPAW_API void pawmtx_free( pawmtx mtx )
    {
    	pawd err = rawpaw_lock_mutex();
    	if ( err )
    		return;
    	if ( mtx < 0 || mtx >= mutexcount || mutices[mtx].tid != pawgetid() )
    	{
    		rawpaw_free_mutex();
    		return;
    	}
    	mutices[mtx].locks--;
    	if ( mutices[mtx].locks < 1 )
    		mutices[mtx].tid = -1;
    	rawpaw_free_mutex();
    }
    RAWPAW_API pawd pawmtx_lock( pawmtx mtx, pawjd wait )
    {
    	pawd err = 0;
    	pawjd tid = pawgetid();
    	pawjd tics = 0, prev = clock(), next = 0;
    	while ( wait < 0 || tics < wait )
    	{
    		err = rawpaw_lock_mutex();
    		if ( err )
    			return err;
    		if ( mtx < 0 || mtx >= mutexcount || !(mutices[mtx].taken) )
    		{
    			rawpaw_free_mutex();
    			return -1;
    		}
    		if ( mutices[mtx].tid < 0 )
    		{
    			mutices[mtx].tid  = tid;
    			rawpaw__free_mutex();
    			return 0;
    		}
    		rawpaw__free_mutex();
    		next = clock();
    		tics += next - prev;
    		prev = next;
    	}
    	return ETIMEDOUT;
    }

  13. #13
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Realised it now that I've gotten round to trying to compile it that there're compile time errors, just put those aside, the only issues I'm looking for peops to point out are runtime ones based on the logic being used

  14. #14
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    it's impossible to make sure the dev doesn't do exactly that
    It's impossible to make sure that malloc() is used without free(). But it's contracted in the docs that the caller of malloc() is responsible to call free() for the deallocation. What did you contract in the docs of your lib?

  15. #15
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    Quote Originally Posted by aGerman View Post
    It's impossible to make sure that malloc() is used without free(). But it's contracted in the docs that the caller of malloc() is responsible to call free() for the deallocation. What did you contract in the docs of your lib?
    I just setup a allocation callback that's responsible for sorting that out under the hood, similar to realloc but takes a "ud" parameter like lua's does, the default just calls malloc & free under the hood

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Help with counting problem
    By ownerpurez in forum C++ Programming
    Replies: 5
    Last Post: 02-12-2013, 04:30 AM
  2. Help with word counting problem
    By optimizer_777 in forum C Programming
    Replies: 6
    Last Post: 01-20-2012, 04:46 PM
  3. Problem with counting
    By jinjinodie in forum C++ Programming
    Replies: 5
    Last Post: 10-23-2010, 10:05 AM
  4. Similar thread: More token counting
    By Imanuel in forum C Programming
    Replies: 3
    Last Post: 07-21-2010, 04:54 AM
  5. counting problem
    By javani in forum C Programming
    Replies: 16
    Last Post: 04-03-2007, 11:25 PM

Tags for this Thread