pthread_attr_getstacksize problem

Recently, the company encountered a thread stack size problem, and I took this opportunity to learn about the functions related to the thread stack size. If the company still uses older code, it uses the pthread library to support threads instead of the thread class in C++11. There are mainly two related functions: pthread_attr_setstacksize() and pthread_attr_getstacksize().

Let’s take a look at a simple example:

#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)

static void *thread_start(void *arg)
{
size_t stack_size = 0;
pthread_attr_t attr;
int ret = pthread_attr_init( & amp;attr);
if(ret != 0)
{
handle_error_en(ret, "pthread_attr_init");
}
   ret = pthread_attr_getstacksize( & amp;attr, & amp;stack_size);
   if (ret != 0)
   {
handle_error_en(ret, "pthread_attr_setstacksize");
   }
printf("stack_size = %lu\\
", stack_size);
   return 0;
}

int main(int argc, char *argv[])
{
   int s, tnum, opt, num_threads;
   pthread_t thread_id;
   pthread_attr_t attr;
   int stack_size;
   void *res;

   /* The "-s" option specifies a stack size for our threads */
   stack_size = -1;
   while ((opt = getopt(argc, argv, "s:")) != -1)
   {
switch (opt)
{
case 's':
stack_size = strtoul(optarg, NULL, 0);
break;

default:
fprintf(stderr, "Usage: %s [-s stack-size] arg...\\
", argv[0]);
exit(EXIT_FAILURE);
}
   }

   /* Initialize thread creation attributes */
   s = pthread_attr_init( & amp;attr);
   if (s != 0)
   {
handle_error_en(s, "pthread_attr_init");
   }

   if (stack_size > 0)
   {
s = pthread_attr_setstacksize( & amp;attr, stack_size);
if (s != 0)
{
handle_error_en(s, "pthread_attr_setstacksize");
}
   }


printf("set stack size %lu\\
", stack_size);
   s = pthread_create( & amp;thread_id, & amp;attr, & amp;thread_start, & amp;thread_id);
   if (s != 0)
   {
handle_error_en(s, "pthread_create");
   }

   /* Destroy the thread attributes object, since it is no
longer needed */
   s = pthread_attr_destroy( & amp;attr);
   if (s != 0)
   {
handle_error_en(s, "pthread_attr_destroy");
   }

   s = pthread_join(thread_id, & amp;res);
   if (s != 0)
   {
handle_error_en(s, "pthread_join");
   }

   exit(EXIT_SUCCESS);
}

The thread stack size is set to 512 Kb in the code, but the size obtained by using pthread_attr_getstacksize() is 8MB. why? In fact, this 8388608 is the default thread stack size of the system. You can use ulimit -a to view relevant information:

Find the source code of pthread_attr_getstacksize and look at it. You will probably know why it has this return value.

/* Copyright (C) 2002-2022 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   Lesser General Public License for more details.
   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <https://www.gnu.org/licenses/>. */
#include "pthreadP.h"
#include <shlib-compat.h>
int
__pthread_attr_getstacksize (const pthread_attr_t *attr, size_t *stacksize)
{
  struct pthread_attr *iattr;
  iattr = (struct pthread_attr *) attr;
  size_t size = iattr->stacksize;
  /* If the user has not set a stack size we return what the system
     will use as the default. */
  if(size==0)
    {
      lll_lock (__default_pthread_attr_lock, LLL_PRIVATE);
      size = __default_pthread_attr.internal.stacksize;
      lll_unlock (__default_pthread_attr_lock, LLL_PRIVATE);
    }
  *stacksize = size;
  return 0;
}
versioned_symbol (libc, __pthread_attr_getstacksize,
                  pthread_attr_getstacksize, GLIBC_2_34);
#if OTHER_SHLIB_COMPAT (libpthread, GLIBC_2_1, GLIBC_2_34)
compat_symbol (libpthread, __pthread_attr_getstacksize,
               pthread_attr_getstacksize, GLIBC_2_1);
#endif

You can see that if the user does not set the stack size, it will return to the system default value, which is 8MB. But in the above code, pthread_attr_setstacksize() has been called to set the stack size. Why does it return the default value? Look carefully at the implementation of pthread_attr_getstacksize. The stack size it returns is actually the stacksize value taken from the input parameter attr. So back to our code, we use pthread_attr_init( &attr) to initialize attr, so the returned value is also It is the stacksize value in attr after initialization. You can take a look at the source code of pthread_attr_init:

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "pthreadP.h"
#include <shlib-compat.h>
struct pthread_attr *__attr_list;
int __attr_list_lock = LLL_LOCK_INITIALIZER;
int
__pthread_attr_init (pthread_attr_t *attr)
{
  struct pthread_attr *iattr;
  ASSERT_TYPE_SIZE (pthread_attr_t, __SIZEOF_PTHREAD_ATTR_T);
  ASSERT_PTHREAD_INTERNAL_SIZE (pthread_attr_t, struct pthread_attr);
  /* Many elements are initialized to zero so let us do it all at
     once. This also takes care of clearing the bytes which are not
     internally used. */
  memset (attr, '\0', __SIZEOF_PTHREAD_ATTR_T);
  iattr = (struct pthread_attr *) attr;
  /* Default guard size specified by the standard. */
  iattr->guardsize = __getpagesize ();
  return 0;
}
libc_hidden_def (__pthread_attr_init)
versioned_symbol (libc, __pthread_attr_init, pthread_attr_init, GLIBC_2_1);
#if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_1)
int
__pthread_attr_init_2_0 (pthread_attr_t *attr)
{
  /* This code is specific to the old LinuxThread code which has a too
     small pthread_attr_t definition. The struct looked like
     this: */
  struct old_attr
  {
    int detachstate;
    int schedpolicy;
    struct sched_param schedparam;
    int inheritsched;
    int scope;
  };
  struct pthread_attr *iattr;
  /* Many elements are initialized to zero so let us do it all at
     once. This also takes care of clearing the bytes which are not
     internally used. */
  memset (attr, '\0', sizeof (struct old_attr));
  iattr = (struct pthread_attr *) attr;
  iattr->flags |= ATTR_FLAG_OLDATTR;
  /* We cannot enqueue the attribute because that member is not in the
     old attribute structure. */
  return 0;
}
compat_symbol (libc, __pthread_attr_init_2_0, pthread_attr_init, GLIBC_2_0);
#endif

Just give the input parameter attr to memset, so in pthead_attr_getstacksize(), size = iattr->stacksize is 0, so what pthead_attr_getstacksize gets is the default stack size. So how to actually get the stacksize of the current thread? Use the pthread_getattr_np() function to get the attr of the specified thread, such as:

static void *thread_start(void *arg)
{
size_t stack_size = 0;
pthread_attr_t attr;
int ret = pthread_getattr_np(pthread_self(), & amp;attr);
if(ret != 0)
{
handle_error_en(ret, "pthread_attr_init");
}
   ret = pthread_attr_getstacksize( & amp;attr, & amp;stack_size);
   if (ret != 0)
   {
handle_error_en(ret, "pthread_attr_setstacksize");
   }
printf("current thread stack_size = %lu\\
", stack_size);
   return 0;
}