在这个练习中(生产者-消费者),3个生产者各是一个线程,产生质数。但是当我运行这个程序时,第一个被消耗的素数并不是它产生的第一个素数,所以我没有得到预期的输出。你能帮我找到并改正这个错误吗?
下面是模式的主文件:#include <stdio.h>
#include "oslab_lowlevel_h.h"
int NextPrime( int );
#define FIFO_SIZE 10
/* Declare a structure to hold a producer's starting value,
* and an integer for the Producer-number (Producer 1, 2 or 3). */
struct Prod {
int startvalue;
int id;
};
unsigned int stack1[0x400]; /* Stack for thread 1 */
unsigned int stack2[0x400]; /* Stack for thread 2 */
unsigned int stack3[0x400]; /* Stack for thread 3 */
unsigned int stack4[0x400]; /* Stack for thread 4 */
unsigned int stack5[0x400]; /* Stack for thread 5 */
/* Declare variables for the First-In-First-Out Queue */
int Fifo[FIFO_SIZE]; /* Array holding FIFO queue data. */
int rdaddr; /* Next unread entry when reading from queue. */
int wraddr; /* Next free entry when writing into queue. */
/* Declaration of semaphore variables.
*
* Sorry for the lack of comments, but part of the purpose of the lab
* is that you should find things out by reading the actual code. */
int rdmutex = 1;
int wrmutex = 1;
int nrempty = FIFO_SIZE;
int nrfull = 0;
/*
* fatal_error
*
* Print a message, then stop execution.
* This function never returns; after printing
* the message, it enters an infinite loop.
*/
void fatal_error( char * msg)
{
printf( "nFatal error: %sn", msg );
while( 1 );
}
/*
* Sleep
*
* Delay execution by keeping the CPU busy for a while,
* counting down to zero.
*/
void Sleep (int n)
{
while (n--);
}
/*
* Signal
*
* Semaphore operation: add to semaphore,
* possibly allowing other threads to continue.
*/
void Signal( int *sem )
{
/* We must disable interrupts, since the operation
* *sem = *sem + 1
* will require several machine instructions on Nios2.
* If we have a timer-interrupt and a thread-switch
* somewhere in the middle of those machine instructions,
* the semaphore will be updated twice, or not at all, or
* in some other erroneous way.
*/
oslab_begin_critical_region();
*sem = *sem + 1;
oslab_end_critical_region();
}
/*
* Wait
*
* Sempahore operation: check semaphore, and
* wait if the semaphore value is zero or less.
*/
void Wait( int *sem )
{
/* Disable interrupts. */
oslab_begin_critical_region();
while ( *sem <= 0 )
{
/* If we should wait, enable interrupts again. */
oslab_end_critical_region();
// oslab_yield(); /* Perhaps we should yield here? */
/* Disable interrupts again before next iteration in loop. */
oslab_begin_critical_region();
}
/* We have waited long enough - the semaphore-value is now
* greater than zero. Decrease it. */
*sem = *sem - 1;
/* Enable interrupts again. */
oslab_end_critical_region();
}
/*
* PutFifo
*
* Insert an integer into the FIFO queue.
*/
void PutFifo( int tal )
{
// Wait (&nrempty); /* Wait for nrempty? */
// Wait (&wrmutex); /* Wait for wrmutex? */
Fifo[wraddr] = tal; /* Write to FIFO array. */
// printf("nPutFifo: %d ", tal); /* Optional debug output */
// printf("nwraddr = %d ", wraddr); /* Optional debug output. */
wraddr = wraddr + 1; /* Increase index into FIFO array,
to point to the next free position. */
/* Wrap around the index, if it has reached the end of the array. */
if (wraddr == FIFO_SIZE ) wraddr = 0;
// Signal (&wrmutex); /* Signal wrmutex? */
// Signal (&nrfull); /* Signal nrfull? */
}
/*
* GetFifo
*
* Extract the next integer from the FIFO queue.
*/
int GetFifo( void )
{
int retval; /* Declare temporary for return value. */
// Wait (&nrfull); /* Wait for nrfull? */
// Wait (&rdmutex); /* Wait for rdmutex? */
retval = Fifo[rdaddr]; /* Get value from FIFO array. */
// printf("nGetFifo: %d ", retval); /* Optional debug output */
// printf("nrdaddr = %d ", rdaddr); /* Optional debug output */
rdaddr = rdaddr + 1; /* Increase index into FIFO array,
to point to the next free position. */
/* Wrap around the index, if it has reached the end of the array. */
if (rdaddr == FIFO_SIZE ) rdaddr = 0;
// Signal (&rdmutex); /* Signal rdmutex? */
// Signal (&nrempty); /* Signal nrempty? */
return (retval); /* Return value fetched from FIFO. */
}
/*
* NextPrime
*
* Return the first prime number larger than the integer
* given as a parameter. The integer must be positive.
*
* *** NextPrime is outside the focus of this assignment. ***
* The definition of NextPrime can be found at the end of this file.
* The short declaration here is required by the compiler.
*/
int NextPrime( int );
void Producer( struct Prod * prodstruct )
{
int next; /* Will hold the prime we just produced. */
int prodid; /* Tells whether we are producer 1, 2 or 3. */
next = prodstruct -> startvalue; /* Get starting value from parameter. */
prodid = prodstruct -> id;/* Get producer number from parameter. */
while( 1 ) /* Loop forever. */
{
next = NextPrime (next);/* Produce a new prime. */
printf("nNext Prime from producer %d is %d",prodid,next); /* Informational output. */
PutFifo(next); /* Write prime into FIFO. */
// oslab_yield(); /* Perhaps we should yield here? */
}
}
void Consumer( int * tal )
{
int next; /* Will hold the prime we are to consume. */
int consid = *tal; /* Tells whether we are consumer 1 or 2. */
while( 1 ) /* Loop forever. */
{
next = GetFifo(); /* Get a newly produced prime from the FIFO. */
printf("nConsumer %d gets Prime %d ",consid, next); /* Informational output. */
Sleep(2000); /* Symbolic work. */
// oslab_yield(); /* Perhaps we should yield here? */
}
}
int main( void )
{
int new_thread_id; /* Thread ID variable. */
struct Prod prod1, prod2, prod3; /* Producer starting-values. */
int cons1, cons2; /* Consumer starting-values. */
rdaddr = 0; /* FIFO initialization. */
wraddr = 0; /* FIFO initialization. */
printf("nSystem starting...");
prod1.startvalue = 2000;
prod1.id = 1;
prod2.startvalue = 5000;
prod2.id = 2;
prod3.startvalue = 8000;
prod3.id = 3;
cons1 = 1;
cons2 = 2;
new_thread_id = oslab_create_thread((void *)Producer, &prod1, &(stack1[0x3ff]));
if( new_thread_id < 0 ) fatal_error( "cannot start Producer 1" );
printf("nProducer %d is created with thread-ID %d", prod1.id, new_thread_id);
new_thread_id = oslab_create_thread((void *)Producer, &prod2, &(stack2[0x3ff]));
if( new_thread_id < 0 ) fatal_error( "cannot start Producer 2" );
printf("nProducer %d is created with thread-ID %d", prod2.id, new_thread_id);
new_thread_id = oslab_create_thread((void *)Producer, &prod3, &(stack3[0x3ff]));
if( new_thread_id < 0 ) fatal_error( "cannot start Producer 3" );
printf("nProducer %d is created with thread-ID %d", prod3.id, new_thread_id);
new_thread_id = oslab_create_thread((void *)Consumer, &cons1, &(stack4[0x3ff]));
if( new_thread_id < 0 ) fatal_error( "cannot start Consumer 1" );
printf("nConsumer %d is created with thread-ID %d", cons1, new_thread_id);
new_thread_id = oslab_create_thread((void *)Consumer, &cons2, &(stack5[0x3ff]));
if( new_thread_id < 0 ) fatal_error( "cannot start Consumer 2" );
printf("nConsumer %d is created with thread-ID %d", cons2, new_thread_id);
oslab_idle(); /* Must be called here! */
}
/*
* NextPrime
*
* Return the first prime number larger than the integer
* given as a parameter. The integer must be positive.
*/
#define PRIME_FALSE 0 /* Constant to help readability. */
#define PRIME_TRUE 1 /* Constant to help readability. */
int NextPrime( int inval )
{
int perhapsprime; /* Holds a tentative prime while we check it. */
int testfactor; /* Holds various factors for which we test perhapsprime. */
int found; /* Flag, false until we find a prime. */
if (inval < 3 ) /* Initial sanity check of parameter. */
{
if(inval <= 0) return(1); /* Return 1 for zero or negative input. */
if(inval == 1) return(2); /* Easy special case. */
if(inval == 2) return(3); /* Easy special case. */
}
else
{
/* Testing an even number for primeness is pointless, since
* all even numbers are divisible by 2. Therefore, we make sure
* that perhapsprime is larger than the parameter, and odd. */
perhapsprime = ( inval + 1 ) | 1 ;
}
/* While prime not found, loop. */
for( found = PRIME_FALSE; found != PRIME_TRUE; perhapsprime += 2 )
{
/* Check factors from 3 up to perhapsprime/2. */
for( testfactor = 3; testfactor <= (perhapsprime >> 1) + 1; testfactor += 1 )
{
found = PRIME_TRUE; /* Assume we will find a prime. */
if( (perhapsprime % testfactor) == 0 ) /* If testfactor divides perhapsprime... */
{
found = PRIME_FALSE; /* ...then, perhapsprime was non-prime. */
goto check_next_prime; /* Break the inner loop, go test a new perhapsprime. */
}
}
check_next_prime:; /* This label is used to break the inner loop. */
if( found == PRIME_TRUE ) /* If the loop ended normally, we found a prime. */
{
return( perhapsprime ); /* Return the prime we found. */
}
}
return( perhapsprime ); /* When the loop ends, perhapsprime is a real prime. */
}
其余文件可在此处获得。
当我运行代码时,我得到了生产者的预期输出,但我没有得到消费者的预期输出:
System starting...
Producer 1 is created with thread-ID 1
Producer 2 is created with thread-ID 2
Producer 3 is created with thread-ID 3
Consumer 1 is created with thread-ID 4
Consumer 2 is created with thread-ID 5
#### Thread yielded after using 1 tick.
Performing thread-switch number 1. The system has been running for 1 ticks.
Switching from thread-ID 0 to thread-ID 1.
Next Prime from producer 1 is 2003
PutFifo: 2003
wraddr = 0
Next Prime from producer 1 is 2011
PutFifo: 2011
wraddr = 1
Next Prime from producer 1 is 2017
PutFifo: 2017
wraddr = 2
Next Prime from producer 1 is 2027
PutFifo: 2027
wraddr = 3
Next Prime from producer 1 is 2029
PutFifo: 2029
wraddr = 4
Next Prime from producer 1 is 2039
PutFifo: 2039
wraddr = 5
Next Prime from producer 1 is 2053
PutFifo: 2053
wraddr = 6
Next Prime from producer 1 is 2063
PutFifo: 2063
wraddr = 7
Next Prime from producer 1 is 2069
PutFifo: 2069
wraddr = 8
Next Prime from producer 1 is 2081
PutFifo: 2081
wraddr = 9
Next Prime from producer 1 is 2083
PutFifo: 2083
wraddr = 0
Next Prime from producer 1 is 2087
PutFifo: 2087
wraddr = 1
Next Prime from producer 1 is 2089
PutFifo: 2089
wraddr = 2
Next Prime from producer 1 is 2099
PutFifo: 2099
wraddr = 3
Next Prime from producer 1 is 2111
PutFifo: 2111
wraddr = 4
Next Prime from producer 1 is 2113
PutFifo: 2113
wraddr = 5
Next Prime from producer 1 is 2129
PutFifo: 2129
wraddr = 6
Next Prime from producer 1 is 2131
PutFifo: 2131
wraddr = 7
Next Prime from producer 1 is 2137
PutFifo: 2137
wraddr = 8
Next Prime from producer 1 is 2141
PutFifo: 2141
wraddr = 9
Next Prime from producer 1 is 2143
PutFifo: 2143
wraddr = 0
Next Prime from producer 1 is 2153
PutFifo: 2153
wraddr = 1
Performing thread-switch number 2. The system has been running for 101 ticks.
Switching from thread-ID 1 to thread-ID 2.
Next Prime from producer 2 is 5003
PutFifo: 5003
wraddr = 2
Next Prime from producer 2 is 5009
PutFifo: 5009
wraddr = 3
Next Prime from producer 2 is 5011
PutFifo: 5011
wraddr = 4
Next Prime from producer 2 is 5021
PutFifo: 5021
wraddr = 5
Next Prime from producer 2 is 5023
PutFifo: 5023
wraddr = 6
Next Prime from producer 2 is 5039
PutFifo: 5039
wraddr = 7
Next Prime from producer 2 is 5051
PutFifo: 5051
wraddr = 8
Next Prime from producer 2 is 5059
PutFifo: 5059
wraddr = 9
Next Prime from producer 2 is 5077
PutFifo: 5077
wraddr = 0
Next Prime from producer 2 is 5081
PutFifo: 5081
wraddr = 1
Performing thread-switch number 3. The system has been running for 201 ticks.
Switching from thread-ID 2 to thread-ID 3.
Next Prime from producer 3 is 8009
PutFifo: 8009
wraddr = 2
Next Prime from producer 3 is 8011
PutFifo: 8011
wraddr = 3
Next Prime from producer 3 is 8017
PutFifo: 8017
wraddr = 4
Next Prime from producer 3 is 8039
PutFifo: 8039
wraddr = 5
Next Prime from producer 3 is 8053
PutFifo: 8053
wraddr = 6
Next Prime from producer 3 is 8059
PutFifo: 8059
wraddr = 7
Performing thread-switch number 4. The system has been running for 301 ticks.
Switching from thread-ID 3 to thread-ID 4.
GetFifo: 5077
rdaddr = 0
Consumer 1 gets Prime 5077
GetFifo: 5081
rdaddr = 1
Consumer 1 gets Prime 5081
GetFifo: 8009
rdaddr = 2
Consumer 1 gets Prime 8009
GetFifo: 8011
rdaddr = 3
Consumer 1 gets Prime 8011
GetFifo: 8017
rdaddr = 4
Consumer 1 gets Prime 8017
GetFifo: 8039
rdaddr = 5
Consumer 1 gets Prime 8039
GetFifo: 8053
rdaddr = 6
Consumer 1 gets Prime 8053
GetFifo: 8059
rdaddr = 7
Consumer 1 gets Prime 8059
GetFifo: 5051
rdaddr = 8
Consumer 1 gets Prime 5051
GetFifo: 5059
rdaddr = 9
Consumer 1 gets Prime 5059
GetFifo: 5077
rdaddr = 0
Consumer 1 gets Prime 5077
GetFifo: 5081
你能告诉我为什么前30个质数会被覆盖,而我所做的只是遵循规范,只是从代码中删除注释来激活教师为我们准备的学习内容吗?几个月来我一直无法完成这个练习,因为我没有得到任何好的帮助。前30个质数被严重覆盖,程序不应该被改变(这是家庭作业)。我问了老师,他没有,他说我用的是新版本的软件。我可以尝试使用旧版本的软件,但这似乎不是一个可行的解决方案。
更新我能想到的策略是开始使用调试器并在执行期间检查FIFO ADT。我没有太多的经验使用gdb所以请帮助我,如果你可以。
2013-05-05
在评论中,我注意到:
你有wrap控制,所以当写入器到达数组的末尾时,下一个条目被放在开始位置,但是你没有任何overfill控制,所以如果生产者生成的速度比消费者消耗的快,生产者覆盖未读数据。需要确保阵列
Fifo
没有被填满。
Nick Rosencrantz观察到:
你是对的,将FIFO大小增加到100修复了这个错误。
实际上,增加FIFO大小不修复错误;它只是在更长的时间内避免了这个bug。您需要跟踪写指针(索引)是否会赶上读指针,并且要么不添加新数字,要么延迟添加新数字,直到其中一个消费者读取了读指针上的数字,以便再次有空间。
代码的实际测试
问题中的代码是逐字取自练习的代码。我不确定所描述的设备是什么,但屏幕截图显示涉及Windows。从表面上看,这使得测试代码变得困难——其中一个文件是汇编文件。但是,这些代码可以很容易地通过原语的Unix (POSIX pthread)实现进行补充。实际上,这个实验室设计得很好;做这个模拟很容易。然而,你必须对我报告的任何结果持保留态度——Mac是一种非常不同的机器。
-
我更喜欢在定义或使用之前声明函数。
#include <unistd.h> extern void fatal_error(char * msg); extern void Sleep (int n); extern void Signal(int *sem); extern void Wait(int *sem); extern void PutFifo(int tal); extern int GetFifo(void); extern void Producer(struct Prod * prodstruct); extern void Consumer(int * tal);
-
fatal_error()
中的while (1);
环为忙等待;我宁愿用pause()
。然而,它从来没有真正使用过,所以也许这并不重要。 -
必要的
oslab_*()
原语可以用POSIX pthreads简单地模拟:/* pthread implementation */ #include <pthread.h> #include <time.h> static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; void oslab_begin_critical_region(void) { pthread_mutex_lock(&mtx); } void oslab_end_critical_region(void) { pthread_mutex_unlock(&mtx); } int oslab_create_thread(int (*thread_function)(void *), void *data, unsigned int *stack) { typedef void *(*ThreadMain)(void *); static int threadnum = 0; pthread_t pth; pthread_attr_t pat; pthread_attr_init(&pat); pthread_attr_setdetachstate(&pat, PTHREAD_CREATE_DETACHED); if (pthread_create(&pth, &pat, (ThreadMain)thread_function, data) != 0) { char buffer[128]; sprintf(buffer, "Failed to create thread with stack %pn", stack); fatal_error(buffer); } return ++threadnum; } void oslab_idle(void) { pause(); } void oslab_yield(void) { struct timespec rqtp = { .tv_sec = 0, .tv_nsec = 1000000 }; // 1 millisecond if (nanosleep(&rqtp, 0) != 0) fatal_error("nanosleep failedn"); } /* end pthread implementation */
-
所有消息都是令人恼火的
"nMessage"
格式;这是一种奇怪的格式。将它们全部重写,使换行符位于行尾,'end of line'字符总是应该在这里。 -
当调试打印被启用时,这些行应该被强制输出。在调试打印语句之后,我使用了
fflush(0)
(在此上下文中相当于fflush(stdout)
)。另一个(更好的)方法是在main()
中调用setvbuf()
来设置行缓冲。char buffer[BUFSIZ]; setvbuf(stdout, buffer, _IOLBF, BUFSIZ);
有了这些变化(留下同步原语——对Wait()
、Signal()
和oslab_yield()
的调用——被注释掉),一切都乱套了:
System starting...
Producer 1 is created with thread-ID 1
Producer 2 is created with thread-ID 2
Producer 3 is created with thread-ID 3
Consumer 1 is created with thread-ID 4
Consumer 2 is created with thread-ID 5
Consumer 1 gets Prime 0
Next Prime from producer 1 is 2003
Consumer 2 gets Prime 0
Next Prime from producer 2 is 5003
Next Prime from producer 3 is 8009
Consumer 1 gets Prime 0
Next Prime from producer 1 is 2011
Consumer 2 gets Prime 0
Next Prime from producer 2 is 5009
Consumer 1 gets Prime 0
Next Prime from producer 1 is 2017
Consumer 2 gets Prime 0
Next Prime from producer 3 is 8011
Next Prime from producer 2 is 5011
Consumer 1 gets Prime 0
Next Prime from producer 1 is 2027
Consumer 2 gets Prime 0
Consumer 1 gets Prime 0
Next Prime from producer 3 is 8017
Next Prime from producer 2 is 5021
Next Prime from producer 1 is 2029
Consumer 2 gets Prime 0
Consumer 1 gets Prime 2003
Consumer 2 gets Prime 2029
Next Prime from producer 1 is 2039
Next Prime from producer 3 is 8039
Next Prime from producer 2 is 5023
Consumer 1 gets Prime 8009
Consumer 2 gets Prime 2011
然而,如果您启用同步原语(但禁用调试代码),那么您将获得相同的行为:在队列中有数据之前,消费者不会尝试读取队列,并且当队列中没有空间时,写入器不会尝试向队列写入。
System starting...
Producer 1 is created with thread-ID 1
Producer 2 is created with thread-ID 2
Producer 3 is created with thread-ID 3
Consumer 1 is created with thread-ID 4
Consumer 2 is created with thread-ID 5
Next Prime from producer 1 is 2003
Next Prime from producer 2 is 5003
Next Prime from producer 3 is 8009
Consumer 1 gets Prime 2003
Consumer 2 gets Prime 5003
Next Prime from producer 1 is 2011
Next Prime from producer 3 is 8011
Next Prime from producer 2 is 5009
Next Prime from producer 2 is 5011
Consumer 1 gets Prime 8009
Consumer 2 gets Prime 2011
Next Prime from producer 1 is 2017
Next Prime from producer 3 is 8017
Consumer 1 gets Prime 8011
Consumer 2 gets Prime 5009
Next Prime from producer 2 is 5021
Next Prime from producer 1 is 2027
Next Prime from producer 3 is 8039
Next Prime from producer 2 is 5023
Consumer 1 gets Prime 5011
Consumer 2 gets Prime 2017
Next Prime from producer 3 is 8053
Next Prime from producer 1 is 2029
Next Prime from producer 2 is 5039
这是预料之中的。如果你有真正的多核线程(Intel Core i7有),那么没有同步,你就会得到各种奇怪的行为。有了同步,一切都很平静。我让代码自由运行,输出到文件中。当对结果进行分析时,您会看到每个质数2003..4999,每个质数5003出现两次…从8009开始,每个质数出现三次,这是您所期望的。
如果您启用调试代码,您将看到更多输出:
System starting...
Producer 1 is created with thread-ID 1
Next Prime from producer 1 is 2003
Producer 2 is created with thread-ID 2
PutFifo: 2003
wraddr = 0
Producer 3 is created with thread-ID 3
Consumer 1 is created with thread-ID 4
GetFifo: 2003
rdaddr = 0
Consumer 2 is created with thread-ID 5
Next Prime from producer 2 is 5003
Next Prime from producer 3 is 8009
Consumer 1 gets Prime 2003
PutFifo: 5003
wraddr = 1
GetFifo: 5003
rdaddr = 1
Consumer 1 gets Prime 5003
Next Prime from producer 1 is 2011
PutFifo: 2011
wraddr = 2
GetFifo: 2011
rdaddr = 2
Consumer 2 gets Prime 2011
PutFifo: 8009
wraddr = 3
Next Prime from producer 2 is 5009
PutFifo: 5009
wraddr = 4
GetFifo: 8009
rdaddr = 3
Consumer 1 gets Prime 8009
Next Prime from producer 3 is 8011
GetFifo: 5009
PutFifo: 8011
rdaddr = 4
Next Prime from producer 1 is 2017
wraddr = 5
Consumer 2 gets Prime 5009
Next Prime from producer 2 is 5011
PutFifo: 5011
wraddr = 6
PutFifo: 2017
wraddr = 7
GetFifo: 8011
rdaddr = 5
Consumer 2 gets Prime 8011
GetFifo: 5011
rdaddr = 6
Next Prime from producer 3 is 8017
Consumer 1 gets Prime 5011
PutFifo: 8017
wraddr = 8
Next Prime from producer 2 is 5021
PutFifo: 5021
wraddr = 9
Next Prime from producer 1 is 2027
PutFifo: 2027
wraddr = 0
GetFifo: 2017
rdaddr = 7
Consumer 1 gets Prime 2017
显示wraddr
从9换行到0。否则,它是冗长和乏味的。
不清楚您是否能够在原始环境中的程序上运行GDB。您正在使用自制线程(如果您使用原始oslab_lowlevel_c.c
和oslab_lowlevel_asm.s
源代码),并且GDB不会意识到多线程。
对于我使用的POSIX线程,可以使用GDB调试代码。
如果不取消PutFifo和GetFifo函数中的Wait和Signal调用的注释,则不可能期望此代码正确工作。如果它在特定的情况下在特定的计算机上运行,那纯粹是运气。
首先,如果一个或多个生产者线程在切换到一个消费者线程之前填满了FIFO缓冲区,那么一些数据显然会丢失。
在示例输出中可以清楚地看到这一点。在第一个消费者线程有机会运行之前,生产者线程已经生成了38个值。因为你的缓冲区大小是10,消费者将要读取的第一个值实际上是产生的第31个值(即最后一个写入wraddr 0的值)。
╔═══════╦════════╦═══════╗
║ count ║ wraddr ║ value ║
╠═══════╬════════╬═══════╣
║ .. ║ .. ║ .. ║
║ 29 ║ 8 ║ 5051 ║
║ 30 ║ 9 ║ 5059 ║
║ 31 ║ 0 ║ 5077 ║ <- Consumer starts here
║ 32 ║ 1 ║ 5081 ║
║ 33 ║ 2 ║ 8009 ║
║ 34 ║ 3 ║ 8011 ║
║ 35 ║ 4 ║ 8017 ║
║ 36 ║ 5 ║ 8039 ║
║ 37 ║ 6 ║ 8053 ║
║ 38 ║ 7 ║ 8059 ║
╚═══════╩════════╩═══════╝
此外,如果消费者线程在切换回一个生产者线程之前读取的数据比FIFO缓冲区中可用的数据多,那么它们将不止一次地读取相同的值。同样,您可以从示例输出中看到这一点。消费者线程读取项目31到38,然后转到项目29和30 (wraddr 8和9处的最后值),然后再次重复项目31。
这还不是最糟糕的事情。在一个真正的抢占式多线程系统中,生产者线程可以在执行PutFifo函数的中途被抢占。因此,假设当wraddr为9时,其中一个生产者线程正在写入FIFO缓冲区。假设它执行这两行。
Fifo[wraddr] = tal; /* Write to FIFO array. */
wraddr = wraddr + 1; /* Increase index into FIFO array,
此时wraddr为10,但在函数有机会检查溢出(并将索引包装回0)之前,线程被另一个生产者线程抢占了。由于wraddr是10,这个新的生产者将会写入超过缓冲区的末尾,这可能会导致应用程序崩溃。
如果存在,wraddr将再次递增(变为11),但它仍然不会换行为零,因为溢出检查期望与FIFO_SIZE精确匹配。因此,即使它没有立即崩溃,它也肯定会在某个时候崩溃,因为wraddr将继续变得越来越大,覆盖越来越多的内存。
底线是,如果你想让这段代码工作,你将不得不添加同步调用。
最简单的方法是使用一个不能是有效生成值的"特殊值",并让生产者只将数据放入具有该特殊值的槽中,如果没有,它将休眠。消费者将消费任何不是特殊值的值,并将该位置设置为特殊值。如果没有非特殊值的数据,消费者将休眠。