এর্নো থ্রেড-নিরাপদ?


175

ইন errno.h, এই ভেরিয়েবলটিকে তাই ঘোষণা করা হয় extern int errno;যেহেতু আমার প্রশ্নটি হ'ল errnoকিছু কল করার পরে মান পরীক্ষা করা বা মাল্টি-থ্রেড কোডে পেরের () ব্যবহার করা কি নিরাপদ? এটি কি কোনও থ্রেড নিরাপদ পরিবর্তনশীল? তা না হলে বিকল্প কী?

আমি x86 আর্কিটেকচারে জিসিসি সহ লিনাক্স ব্যবহার করছি।


5
2 সঠিক বিপরীত উত্তর, আকর্ষণীয় !! ;)
বিনিত ধাত্রক

হেই, আমার উত্তরটি আবার দেখুন। আমি একটি বিক্ষোভ যোগ করেছি যা সম্ভবত বিশ্বাসযোগ্য হবে। :-)
ডিজিটালরোস

উত্তর:


176

হ্যাঁ, এটি থ্রেড নিরাপদ। লিনাক্সে, গ্লোবাল এর্নো ভেরিয়েবল থ্রেড-নির্দিষ্ট। পসিক্সের জন্য ত্রুটিযুক্ত হওয়া উচিত fe

Http://www.unix.org/ whitepapers /reentrant.html দেখুন

POSIX.1 এ, এর্নোকে বহিরাগত বৈশ্বিক চলক হিসাবে সংজ্ঞায়িত করা হয়। তবে এই সংজ্ঞাটি বহুবিবাহিত পরিবেশে অগ্রহণযোগ্য, কারণ এর ব্যবহারের ফলে ননডেটারিস্টিক ফলাফল হতে পারে। সমস্যাটি হ'ল দুটি বা ততোধিক থ্রেডে ত্রুটি দেখা দিতে পারে, যার ফলে একই ত্রুটি সেট করা যায়। এই পরিস্থিতিতে, কোনও থ্রেড অন্য থ্রেড দ্বারা ইতিমধ্যে আপডেট হওয়ার পরে ত্রুটিটি শেষ হতে পারে।

ফলস্বরূপ অদ্বিতীয়ত্মতা রোধ করতে, POSIX.1c এররনোকে একটি পরিষেবা হিসাবে নতুন সংজ্ঞা দেয় যা প্রতি থ্রেড ত্রুটি সংখ্যাটি নীচে অ্যাক্সেস করতে পারে (আইএসও / আইসিসি 9945: 1-1996, §2.4):

কিছু ফাংশন ত্রুটি সংখ্যাটি প্রদান করতে পারে ভেরিয়েবলের মধ্যে প্রতীকটি এরোন দিয়ে প্রবেশ করা। প্রতীক এর্নো সি স্ট্যান্ডার্ড দ্বারা নির্দিষ্ট করা হিসাবে শিরোনামকে অন্তর্ভুক্ত করে সংজ্ঞায়িত করা হয় ... প্রক্রিয়াটির প্রতিটি থ্রেডের জন্য, ইরানের মানটি অন্য থ্রেড দ্বারা ক্রিয়াকলাপ কল বা অ্যাসাইনমেন্ট দ্বারা প্রভাবিত হবে না।

এছাড়াও দেখুন http://linux.die.net/man/3/errno

ত্রুটি-স্থানীয় হয়; এটি একটি থ্রেডে সেট করা অন্য কোনও থ্রেডের মানকে প্রভাবিত করে না।


9
সত্যি? তারা কখন এটা করেছে? আমি যখন সি প্রোগ্রামিং করছিলাম, এর্নোকে বিশ্বাস করা একটি বড় সমস্যা ছিল।
পল টম্বলিন

7
ম্যান, আমার দিনটিতে এটি অনেক ঝামেলা বাঁচাতে পারত।
পল টম্বলিন

4
@ উইনিট: এর্নো আসলে বিট / এর্গনো.ই-তে সংজ্ঞায়িত। অন্তর্ভুক্ত ফাইলটিতে মন্তব্য পড়ুন। এটি বলে: "বিট / এর্নো। দ্বারা ম্যাক্রো হিসাবে সংজ্ঞায়িত না হলে` এরনো 'পরিবর্তনশীল ঘোষণা করুন G এটি জিএনইউ-র ক্ষেত্রে এটি প্রতি থ্রেড ভেরিয়েবল। এটি ম্যাক্রো ব্যবহার করে এই পুনঃসংশোধনটি এখনও কাজ করে তবে এটি কোনও প্রোটোটাইপ ছাড়াই কোনও ফাংশন ঘোষণা হবে এবং এটি একটি -স্ট্রিস্ট-প্রোটোটাইপস সতর্কতা ট্রিগার করতে পারে। "
চার্লস সালভিয়া

2
আপনি যদি লিনাক্স ২.6 ব্যবহার করেন তবে আপনার কিছু করার দরকার নেই। শুধু প্রোগ্রামিং শুরু করুন। :-)
চার্লস সালভিয়া

3
@ উইনিট ধাত্রক হওয়া উচিত # if !defined _LIBC || defined _LIBC_REENTRANT, সাধারণ প্রোগ্রাম সংকলনের সময় _ এলবিসি সংজ্ঞায়িত করা হয় না। যাইহোক, প্রতিধ্বনি চালান #include <errno.h>' | gcc -E -dM -xc - এবং অজানা এবং ছাড়াই পার্থক্যটি দেখুন। ভুল #define errno (*__errno_location ())উভয় ক্ষেত্রেই।
টি

58

হ্যাঁ


এর্নো আর কোনও সাধারণ পরিবর্তনশীল নয়, এটি পর্দার আড়ালে জটিল কিছু, বিশেষত এটি থ্রেড-সেফ হওয়ার জন্য।

দেখুন $ man 3 errno:

ERRNO(3)                   Linux Programmers Manual                  ERRNO(3)

NAME
       errno - number of last error

SYNOPSIS
       #include <errno.h>

DESCRIPTION

      ...
       errno is defined by the ISO C standard to be  a  modifiable  lvalue  of
       type  int,  and  must not be explicitly declared; errno may be a macro.
       errno is thread-local; setting it in one thread  does  not  affect  its
       value in any other thread.

আমরা ডাবল-চেক করতে পারি:

$ cat > test.c
#include <errno.h>
f() { g(errno); }
$ cc -E test.c | grep ^f
f() { g((*__errno_location ())); }
$ 

12

Errno.h এ, এই পরিবর্তনশীলটিকে বহিরাগত ইন্টের্নো হিসাবে ঘোষণা করা হয়;

সি স্ট্যান্ডার্ড যা বলে তা এখানে:

ম্যাক্রোর errnoকোনও বস্তুর শনাক্তকারী হওয়া প্রয়োজন be এটি কোনও ফাংশন কল (উদাহরণস্বরূপ *errno()) এর ফলে পরিবর্তিতযোগ্য লভ্যালুতে প্রসারিত হতে পারে ।

সাধারণত, errnoএকটি ম্যাক্রো যা বর্তমান থ্রেডের জন্য ত্রুটি নম্বরটির ঠিকানা ফিরিয়ে ফাংশনকে কল করে, তারপরে এটিকে অবহেলা করে।

লিনাক্সে আমার যা আছে তা এখানে /usr/incolve/bit/errno.h এ:

/* Function to get address of global `errno' variable.  */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));

#  if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value.  */
#   define errno (*__errno_location ())
#  endif

শেষ পর্যন্ত, এটি এই ধরণের কোড উত্পন্ন করে:

> cat essai.c
#include <errno.h>

int
main(void)
{
    errno = 0;

    return 0;
}
> gcc -c -Wall -Wextra -pedantic essai.c
> objdump -d -M intel essai.o

essai.o:     file format elf32-i386


Disassembly of section .text:

00000000 <main>:
   0: 55                    push   ebp
   1: 89 e5                 mov    ebp,esp
   3: 83 e4 f0              and    esp,0xfffffff0
   6: e8 fc ff ff ff        call   7 <main+0x7>  ; get address of errno in EAX
   b: c7 00 00 00 00 00     mov    DWORD PTR [eax],0x0  ; store 0 in errno
  11: b8 00 00 00 00        mov    eax,0x0
  16: 89 ec                 mov    esp,ebp
  18: 5d                    pop    ebp
  19: c3                    ret

10

অনেকগুলি ইউনিক্স সিস্টেমে থ্রেড- সেফটি -D_REENTRANTনিশ্চিত করে সংকলন করে errno

উদাহরণ স্বরূপ:

#if defined(_REENTRANT) || _POSIX_C_SOURCE - 0 >= 199506L
extern int *___errno();
#define errno (*(___errno()))
#else
extern int errno;
/* ANSI C++ requires that errno be a macro */
#if __cplusplus >= 199711L
#define errno errno
#endif
#endif  /* defined(_REENTRANT) */

1
আমার ধারণা আপনি স্পষ্টভাবে কোড সংকলন করতে হবে না -D_REENTRANT। একই প্রশ্নের জন্য অন্যান্য উত্তরের উপর আলোচনার বিষয়টি উল্লেখ করুন।
বিনিত ধাত্রক

3
@ ভিনিত: এটি আপনার প্ল্যাটফর্মের উপর নির্ভর করে - লিনাক্সে, আপনি সঠিক হতে পারেন; সোলারিসে, আপনি কেবল তখনই সঠিক হতে পারেন যদি আপনি _POSIX_C_SOURCE থেকে 199506 বা তার পরে কোনও সংস্করণ সেট করে থাকেন - সম্ভবত ব্যবহার করে -D_XOPEN_SOURCE=500বা দ্বারা -D_XOPEN_SOURCE=600। সবাই পসিক্স পরিবেশ নির্দিষ্ট করা আছে তা নিশ্চিত করতে বিরক্ত হয় না - এবং তারপরে -D_REENTRANTআপনার বেকন সংরক্ষণ করতে পারে। আপনি পছন্দসই আচরণটি নিশ্চিত করতে আপনাকে অবশ্যই প্রতিটি প্ল্যাটফর্মে - সতর্কতা অবলম্বন করতে হবে।
জোনাথন লেফলার

ডকুমেন্টেশনের একটি অংশ আছে যা নির্দেশ করে যে কোন স্ট্যান্ডার্ড (যেমন: সি 99, এএনএসআই, ইত্যাদি) বা কমপক্ষে কোন সংকলক (যেমন: জিসিসি সংস্করণ এবং পরবর্তী) এই বৈশিষ্ট্যটিকে সমর্থন করে এবং এটি ডিফল্ট কিনা? ধন্যবাদ.
মেঘ

আপনি সি 11 স্ট্যান্ডার্ডটি দেখতে পাচ্ছেন বা এরনোর জন্য POSIX 2008 (2013) এ দেখতে পারেন । সি 11 স্ট্যান্ডার্ড বলছে: ... এবং errnoযা intস্থানীয় স্টোরেজ সময়কাল টাইপ এবং থ্রেডযুক্ত একটি পরিবর্তনযোগ্য লভ্যালু (201) এ প্রসারিত হয় যার মান বিভিন্ন পাঠাগার ফাংশন দ্বারা ধনাত্মক ত্রুটি সংখ্যায় সেট করা হয়। যদি কোনও প্রকৃত অবজেক্ট অ্যাক্সেসের জন্য কোনও ম্যাক্রো সংজ্ঞাটি দমন করা হয় বা কোনও প্রোগ্রাম নামের সাথে একটি সনাক্তকারীকে সংজ্ঞায়িত করে errno, তবে আচরণটি সংজ্ঞায়িত। [... অব্যাহত ...]
জোনাথন লেফলার

[... ধারাবাহিকতা ...] পাদটীকা 201 বলেছেন: ম্যাক্রো errnoকোনও বস্তুর শনাক্তকারী হওয়া উচিত নয়। এটি কোনও ফাংশন কল (উদাহরণস্বরূপ *errno()) এর ফলে পরিবর্তিতযোগ্য লভ্যালুতে প্রসারিত হতে পারে । মূল পাঠ্যটি অব্যাহত থাকে: প্রোগ্রামার শুরুতে প্রাথমিক থ্রেডে এররনের মান শূন্য হয় (অন্যান্য থ্রেডে এরনোর প্রাথমিক মান একটি অনির্দিষ্ট মান) তবে কোনও লাইব্রেরির ক্রিয়াকলাপের মাধ্যমে শূন্যের কাছে সেট করা হয় না। পসিক্স সি 99 স্ট্যান্ডার্ড ব্যবহার করে যা থ্রেডগুলি স্বীকৃতি দেয় না। [... এছাড়াও অবিরত ...]
জোনাথন লেফলার

10

এটি <sys/errno.h>আমার ম্যাক থেকে এসেছে :

#include <sys/cdefs.h>
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS

সুতরাং errnoএখন একটি ফাংশন __error()। থ্রেড-সেফ হতে যাতে ফাংশনটি বাস্তবায়িত হয়।


9

হ্যাঁ , যেমন এরোন ম্যান পৃষ্ঠাতে এবং অন্যান্য জবাবগুলিতে ব্যাখ্যা করা হয়েছে , এরনো হ'ল থ্রেডের স্থানীয় পরিবর্তনশীল।

তবে , একটি নির্বোধ বিবরণ রয়েছে যা সহজেই ভুলে যেতে পারে। প্রোগ্রামগুলিতে কোনও সিস্টেম কল সম্পাদনকারী যে কোনও সিগন্যাল হ্যান্ডলারের ক্রিয়াকলাপটি সংরক্ষণ এবং পুনরুদ্ধার করা উচিত। কারণ সিগন্যালটি এমন কোনও প্রক্রিয়া থ্রেড দ্বারা পরিচালিত হবে যা এর মানটিকে ওভাররাইট করতে পারে।

সুতরাং, সিগন্যাল হ্যান্ডলারের উচিত সংরক্ষণ এবং পুনরুদ্ধার। কিছুটা এইরকম:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}

নিষিদ্ধ → ভুলে গেছি আমি অনুমান করি। আপনি কি এই সিস্টেমে কল সেভ / পুনরুদ্ধার বিশদটির জন্য একটি রেফারেন্স সরবরাহ করতে পারেন?
ক্রেগ ম্যাককুইন

হাই ক্রেগ, টাইপো সম্পর্কিত তথ্যের জন্য ধন্যবাদ, এখন সংশোধন করা হয়েছে। অন্যান্য ইস্যু সম্পর্কে, আমি নিশ্চিত নই যে আপনি কী জিজ্ঞাসা করছেন তা আমি সঠিকভাবে বুঝতে পেরেছি কিনা। সিগন্যাল হ্যান্ডলারটিতে যে কোনও কল যা ত্রুটিটি পরিবর্তন করে তা একই থ্রেডে বাধাগ্রস্ত হওয়া দ্বারা ব্যবহৃত ইররনকে হস্তক্ষেপ করতে পারে (যেমন সিগ_এলআমের অভ্যন্তরে স্ট্র্টল ব্যবহার করা)। ঠিক আছে?
marcmagransdeabril

6

আমি মনে করি উত্তরটি "এটি নির্ভর করে"। থ্রেড-সেফ সি রানটাইম লাইব্রেরিগুলি সাধারণত যদি আপনি সঠিক পতাকাঙ্কিত থ্রেড কোড তৈরি করে থাকেন তবে সাধারণত একটি ফাংশন কল (কোনও ফাংশনে ম্যাক্রো প্রসারিত) হিসাবে এরনো প্রয়োগ করে।


@ টিমো, হ্যাঁ ঠিক আছে, দয়া করে অন্যান্য উত্তরের আলোচনার জন্য উল্লেখ করুন এবং কিছু অনুপস্থিত কিনা তা জানতে দিন।
বিনিত ধাত্রক

3

আমরা একটি মেশিনে একটি সাধারণ প্রোগ্রাম চালিয়ে চেক করতে পারি।

#include <stdio.h>                                                                                                                                             
#include <pthread.h>                                                                                                                                           
#include <errno.h>                                                                                                                                             
#define NTHREADS 5                                                                                                                                             
void *thread_function(void *);                                                                                                                                 

int                                                                                                                                                            
main()                                                                                                                                                         
{                                                                                                                                                              
   pthread_t thread_id[NTHREADS];                                                                                                                              
   int i, j;                                                                                                                                                   

   for(i=0; i < NTHREADS; i++)                                                                                                                                 
   {
      pthread_create( &thread_id[i], NULL, thread_function, NULL );                                                                                            
   }                                                                                                                                                           

   for(j=0; j < NTHREADS; j++)                                                                                                                                 
   {                                                                                                                                                           
      pthread_join( thread_id[j], NULL);                                                                                                                       
   }                                                                                                                                                           
   return 0;                                                                                                                                                   
}                                                                                                                                                              

void *thread_function(void *dummyPtr)                                                                                                                          
{                                                                                                                                                              
   printf("Thread number %ld addr(errno):%p\n", pthread_self(), &errno);                                                                                       
}

এই প্রোগ্রামটি চালাচ্ছেন এবং আপনি প্রতিটি থ্রেডে এর্নোর জন্য বিভিন্ন ঠিকানা দেখতে পাবেন। আমার মেশিনে রানের আউটপুটটি দেখতে এমন দেখাচ্ছে: -

Thread number 140672336922368 addr(errno):0x7ff0d4ac0698                                                                                                       
Thread number 140672345315072 addr(errno):0x7ff0d52c1698                                                                                                       
Thread number 140672328529664 addr(errno):0x7ff0d42bf698                                                                                                       
Thread number 140672320136960 addr(errno):0x7ff0d3abe698                                                                                                       
Thread number 140672311744256 addr(errno):0x7ff0d32bd698 

লক্ষ্য করুন যে ঠিকানাটি সমস্ত থ্রেডের জন্য আলাদা।


যদিও এটি ম্যান পৃষ্ঠাতে সন্ধান করা (বা এসও তে) দ্রুততর, তবুও আপনি এটি যাচাই করার জন্য সময় দিয়েছেন বলে আমি পছন্দ করি। +1 টি।
বায়ো
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.