关于C#:所有被空管道阻塞的线程均被读取

All threads blocked by empty pipe read

我试图自学有关C(Linux)中的多线程和多进程编程的知识。 我写了一个简短的程序,它产生一个新线程,该线程转到一个例程,该例程尝试从空FIFO进行阻塞读取,而主线程继续并打印到STDOUT。 (注意:在执行程序之前,我确实在终端中使用mkfifo newfifo创建了FIFO)

我期望程序打印到屏幕"主线程",然后等待我将数据放入FIFO时阻塞。 而是,整个过程被阻塞,并且仅在将数据放入FIFO后才显示消息"主线程"。

我在这里想念什么吗? 即使生成的线程被阻塞,主线程也不应该继续运行吗? 我尝试使用fork进行测试并创建一个子进程,并得到相同的结果(两个进程都被从空FIFO读取而阻止)。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <pthread.h>

#define NEWPIPE"./newfifo"

typedef struct __attribute__ ((__packed__)) {
  int reserved      :30;
  int threadStarted :1;
  int msgRcved      :1;
} Status;

void* thread_routine(int fd, char* buffer, Status* myStatus)
{
  int great_success = 0;

  myStatus->threadStarted = 1;
  printf("Side thread\
"
);

  great_success = read(fd, buffer, sizeof(buffer));

  if (great_success < 0) {
    printf("pipe failed\
"
);
  } else {
    myStatus->msgRcved = 1;
  }
}

void main()
{
  int fd;
  int cnt = 0;
  char buffer[20];
  Status* myStatus;
  pthread_t thread_id;

  myStatus = (Status*) malloc(sizeof(Status));
  myStatus->reserved      = 0;
  myStatus->threadStarted  = 0;
  myStatus->msgRcved      = 0;

  fd = open(NEWPIPE, O_RDONLY);

  pthread_create(&thread_id,
                  NULL,
                  (void *) thread_routine(fd, buffer, myStatus),
                  NULL);

  printf("Main thread\
"
);

  while (!myStatus->threadStarted) {
    printf("Main thread: side thread started!\
"
);
  }

  while (!myStatus->msgRcved) {
    sleep(1);
    cnt++;
  }

  printf("buffer (cnt = %d): %s\
"
, cnt, buffer);

}

编辑:最新代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <pthread.h>

#define NEWPIPE"./newfifo"

struct Status {
  unsigned int reserved      :30;
  unsigned int threadStarted :1;
  unsigned int msgRcved      :1;
};

void* thread_routine(void *arg)
{
  int great_success = 0;
  int fd;
  char buffer[20];
  struct Status* myStatus;

  fd = open(NEWPIPE, O_RDONLY);

  myStatus = arg;

  myStatus->threadStarted = 1;
  printf("Side thread\
"
);

  while (1) {
    great_success = read(fd, buffer, 20);

    if (great_success < 0) {
      printf("pipe failed\
"
);
    } else {
      printf("buffer : %s\
"
, buffer);
      printf("great_success = %d\
"
, great_success);
      great_success = 0;
    }
  }
}

void main()
{
  int cnt = 0;
  struct Status* myStatus;
  pthread_t thread_id;

  myStatus = (struct Status*) malloc(sizeof(struct Status));
  myStatus->reserved      = 0;
  myStatus->threadStarted  = 0;
  myStatus->msgRcved      = 0;

  pthread_create(&thread_id,
                  NULL,
                  &thread_routine,
                  (void *) myStatus);    // arguments to pass to the function!


  printf("Main thread\
"
);

  while (!myStatus->msgRcved) {
    printf("Main thread: side thread started!\
"
);

    if (myStatus->threadStarted) {
      printf("spawned thread started!\
"
);
    }

    sleep(1);
  }

  pthread_exit(NULL);

}


您正在将调用thread_routine()的结果传递给pthread_create()。必须在调用执行之前对所有参数进行求值,以便在该函数返回之前不会创建线程。大概。因为thread_routine()不会返回(*)(void *),而是pthread_create()会尝试像返回值一样调用返回值,所以整个程序的行为是不确定的。您想传递一个指向该函数的指针,而不是传递该函数的结果:

1
2
3
4
pthread_create(&thread_id,
              NULL,
              thread_routine,
              NULL);

"但是争论呢?"你问?这就引出了下一点:函数thread_routine()对于线程启动例程没有正确的签名。线程启动例程必须接受void *类型的单个参数。 pthread_create()的最后一个参数将作为其(单个)参数传递给指定的函数,并且您可以使该指针指向适当的struct,以代替传递多个单独的参数。

最后,推定线程启动函数需要通过返回指针值(可能是NULL)或调用pthread_exit()退出。当除main()以外的值返回函数到达其终端}而不执行return语句时,行为是不确定的。 (pthread_exit()解决了该问题,因为它不返回。)

请注意,顺便说一句,您的编译器应该吐出一些与此代码有关的警告。您应该始终解决所有编译器警告,或者确定为什么不这样做是安全的。


Instead, the entire process is blocked, and the message"Main thread" only prints after I've put data into the FIFO.

Am I missing something here?

您的主线程在此行被阻止:

1
fd = open(NEWPIPE, O_RDONLY);

因为FIFO的非阻塞,只读打开将阻塞,直到写入器可用为止。您的主线程最终将被解除阻塞,而不是在将数据写入FIFO时,而只是在打开FIFO进行写入时才解除阻塞。

@JohnBollinger在其答案中讨论的代码中还有其他问题。但是,FIFO语义是为什么您看不到预期的初始输出的原因。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
the following is a way to open a named pipe,
so there is no need for any (before running application)
processing needed.

enum enumReturnStatus create_Pipe( INT32 taskSelector  )
{
    enum    enumReturnStatus returnStatus = eRS_Success; // indicate success
    char  *pTask_NameString               = NULL;
    char    Pipe_NameString[ 100 ]        = {'\\0'};
    struct  stat statInfo; // will contain info on a file
                          // and is used to determine if the pipe already exists

    INT32 mkfifoStatus                    = 0;
    INT32 statStatus                      = 0;



    if( 0 >= Pipe_Parameters[ taskSelector ].Pipe_FD )
    { // then pipe not open

        pTask_NameString = get_pTask_NameString( taskSelector );

        if( NULL == pTask_NameString )
        { // task not configured

            return( eRS_Failure );
        }


        /* *********************************************************************
         *  implied else, task configured
         * ********************************************************************
         */



        // create pipe name string
        sprintf( Pipe_NameString,"/tmp/Pipe_2%s", pTask_NameString );



        // check if pipe already exists
        statStatus = stat(Pipe_NameString, &statInfo);

        if( (statStatus)&&(ENOENT == errno) )
        { // then, FIFO pipe does not exist

            // create the pipe
            umask(0);
            // maybe use mknode() instead of mkfifo()
            // mknod(pPipe_name, S_IFIFO | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, 0 );

            // 0666 allows anyone to read/write the pipe
            mkfifoStatus = mkfifo( Pipe_NameString, 0666 );

            if ( -1 == mkfifoStatus )
            {
                CommonWriteCdsLog( eLL_Critical,
                    get_pFormatString(  eFormat_CFFL_string_string ),
                    __FILE__,  __LINE__,
                   "LibFunc:mkfifo() failed to create pipe",
                    strerror( errno ) );

fprintf(stderr,"mkfifo failed: %s \
"
,strerror( errno ));
fflush(stderr);

                system("sync; sync;" );
                exit( EXIT_FAILURE );
            }
        } // endif ( pipe doesn't exist )


        if( !mkfifoStatus && !statStatus )
        { // then pipe created or already exists

            if( 0 >= Pipe_Parameters[taskSelector].Pipe_FD )
            { // then, pipe not yet open

                // note: use O_RDWR so open will not hang
                Pipe_Parameters[taskSelector].Pipe_FD = open( Pipe_NameString, O_CREAT|O_RDWR );

                if( 0 >= Pipe_Parameters[taskSelector].Pipe_FD )
                { // then open failed

                    CommonWriteCdsLog( eLL_Critical,
                        get_pFormatString(  eFormat_CFFL_string_string ),
                        __FILE__,  __LINE__,
                       "LibFunc:open() failed for pipe",
                        strerror( errno ) );
                }

                else
                { // else open successful
                    ;
                } // endif( open for read successful )
            } // endif( pipe not already open )
        } // endif( pipe created or already exists )
    }  // endif( pipe not open )

    return( returnStatus );
} // end create_Pipe()

这段代码:

1
2
3
4
5
typedef struct __attribute__ ((__packed__)) {
    int reserved      :30;
    int threadStarted :1;
    int msgRcved      :1;
} Status;

由于代码正在使用带符号的值,并且结构定义不应该作为typedef进行typedef定义,因此会出现问题:

  • 使代码模糊不清,
  • 在读者中引起混乱
  • 并使编译器名称空间混乱

这是定义结构的首选方法的示例
(并使用位字段纠正问题)

1
2
3
4
5
6
struct status  
{
    unsigned int reserved      :30;
    unsigned int threadStarted :1;
    unsigned int msgRcved      :1;
};

不需要packed属性,因为所有位都适合单个unsigned int存储区。通过变量定义和函数参数列表中的struct status引用此结构。