C 是否有可能用条件变量和信号而不是广播来制造生产者-消费者问题?

C 是否有可能用条件变量和信号而不是广播来制造生产者-消费者问题?,c,multithreading,pthreads,producer-consumer,C,Multithreading,Pthreads,Producer Consumer,我试图在使用pthread\u cond\u signal()而不是pthread\u cond\u broadcast()时运行生产者-消费者问题,但是,我尝试了很多事情,但似乎做不到(如果我选择n生产者和one消费者,则消费者完成,但并非所有生产者都完成,一些生产者被困在满队列中,因此问题永远无法完成执行。我从其他人那里获得了GitHub,我正在尝试编辑它以实现这一点(你可以在附加的链接上查看代码,我会将其粘贴到文章的末尾)。这里的相关部分是生产者和消费者功能,目前我有以下功能: 消费者:

我试图在使用
pthread\u cond\u signal()
而不是
pthread\u cond\u broadcast()
时运行生产者-消费者问题,但是,我尝试了很多事情,但似乎做不到(如果我选择
n
生产者和
one
消费者,则消费者完成,但并非所有生产者都完成,一些生产者被困在满队列中,因此问题永远无法完成执行。我从其他人那里获得了GitHub,我正在尝试编辑它以实现这一点(你可以在附加的链接上查看代码,我会将其粘贴到文章的末尾)。这里的相关部分是生产者和消费者功能,目前我有以下功能:

消费者:

void *consumer (void *carg)
{
  queue  *fifo;
  int     item_consumed;
  pcdata *mydata;
  int     my_tid;
  int    *total_consumed;

  mydata = (pcdata *) carg;

  fifo           = mydata->q;
  total_consumed = mydata->count;
  my_tid         = mydata->tid;

  while (1) {
     pthread_mutex_lock(fifo->mutex); //start of the critical section
 
    while (fifo->empty && *total_consumed != WORK_MAX) {
      printf ("con %d:   EMPTY.\n", my_tid);
      pthread_cond_wait(fifo->notEmpty, fifo->mutex); //if queue is empty then wait for signal that it has something to start consuming
    }

 
    if (*total_consumed >= WORK_MAX) {
       pthread_mutex_unlock(fifo->mutex); //if max work is reached then unlock the mutex and exit
      break;
    }
    queueRemove (fifo, &item_consumed); //reaching this means that queue isn\t empty so just consume
    (*total_consumed)++;
    do_work(CONSUMER_CPU,CONSUMER_CPU);

    printf ("con %d:   %d.\n", my_tid, item_consumed);

    pthread_cond_signal(fifo->notFull); //consumption is done so queue isn't full, signal to producer
    pthread_mutex_unlock(fifo->mutex);
  }

  printf("con %d:   exited\n", my_tid);
  return (NULL);
}

制作人:

void *producer (void *parg)
{
  queue  *fifo;
  int     item_produced;
  pcdata *mydata;
  int     my_tid;
  int    *total_produced;

  mydata = (pcdata *) parg;

  fifo           = mydata->q;
  total_produced = mydata->count;
  my_tid         = mydata->tid;

  while (1) {
      
    pthread_mutex_lock(fifo->mutex);
    do_work(PRODUCER_CPU, PRODUCER_BLOCK);
 
  
    while (fifo->full && *total_produced != WORK_MAX) {
      printf ("prod %d:  FULL.\n", my_tid);
      pthread_cond_wait(fifo->notFull, fifo->mutex); //if queue is full then wait for signal that is not anyone to start producing
  
    }

    if (*total_produced >= WORK_MAX) {
      pthread_mutex_unlock(fifo->mutex); //if total work reached then exit
      break;
    }

    item_produced = (*total_produced)++;
    queueAdd (fifo, item_produced);


    printf("prod %d:  %d.\n", my_tid, item_produced);
    pthread_cond_signal(fifo->notEmpty); //reaching this means we produced something so signal that queue is not empty
    pthread_mutex_unlock(fifo->mutex);
  }

  printf("prod %d:  exited\n", my_tid);
  return (NULL);
}

我的问题是,并不是所有线程都得到退出信号,例如,运行多达13个生产者和一个消费者都正常工作,但超过13个生产者和一个消费者的所有线程都被卡住,下面是一个运行14个生产者和一个消费者的执行示例:

con 0:   EMPTY.
prod 3:  2.
prod 9:  FULL.
prod 11:  FULL.
prod 13:  FULL.
prod 0:  0.
prod 8:  3.
prod 6:  1.
prod 12:  FULL.
prod 7:  4.
prod 10:  FULL.
prod 1:  FULL.
prod 2:  FULL.
prod 5:  FULL.
prod 4:  FULL.
prod 9:  5.
prod 3:  FULL.
prod 0:  FULL.
prod 6:  FULL.
prod 8:  FULL.
prod 7:  FULL.
prod 9:  FULL.
con 0:   0.
prod 11:  6.
prod 13:  FULL.
prod 11:  FULL.
con 0:   1.
prod 12:  7.
prod 10:  FULL.
prod 12:  FULL.
con 0:   2.
prod 1:  8.
prod 2:  FULL.
prod 1:  FULL.
con 0:   3.
prod 4:  9.
prod 5:  FULL.
prod 4:  FULL.
con 0:   4.
prod 3:  10.
prod 0:  FULL.
prod 3:  FULL.
con 0:   5.
prod 6:  11.
prod 8:  FULL.
prod 6:  FULL.
con 0:   6.
prod 7:  12.
prod 9:  FULL.
prod 7:  FULL.
con 0:   7.
prod 13:  13.
prod 11:  FULL.
prod 13:  FULL.
con 0:   8.
prod 12:  14.
prod 10:  FULL.
prod 12:  FULL.
con 0:   9.
prod 2:  15.
prod 1:  FULL.
prod 2:  FULL.
con 0:   10.
prod 5:  16.
prod 4:  FULL.
prod 5:  FULL.
con 0:   11.
prod 3:  17.
prod 0:  FULL.
prod 3:  FULL.
con 0:   12.
prod 8:  18.
prod 6:  FULL.
prod 8:  FULL.
con 0:   13.
prod 9:  19.
prod 7:  FULL.
prod 9:  FULL.
con 0:   14.
prod 11:  20.
prod 13:  FULL.
prod 11:  FULL.
con 0:   15.
prod 10:  21.
prod 12:  FULL.
prod 10:  FULL.
con 0:   16.
prod 2:  22.
prod 1:  FULL.
prod 2:  FULL.
con 0:   17.
prod 4:  23.
prod 5:  FULL.
prod 4:  FULL.
con 0:   18.
prod 0:  24.
prod 3:  FULL.
prod 0:  FULL.
con 0:   19.
prod 6:  25.
prod 8:  FULL.
prod 6:  FULL.
con 0:   20.
prod 7:  26.
prod 9:  FULL.
prod 7:  FULL.
con 0:   21.
prod 11:  27.
prod 13:  FULL.
prod 11:  FULL.
con 0:   22.
prod 12:  28.
prod 10:  FULL.
prod 12:  FULL.
con 0:   23.
prod 1:  29.
prod 2:  exited
prod 1:  exited
con 0:   24.
prod 4:  exited
prod 5:  exited
con 0:   25.
prod 3:  exited
prod 0:  exited
con 0:   26.
prod 8:  exited
prod 6:  exited
con 0:   27.
prod 9:  exited
prod 7:  exited
con 0:   28.
prod 13:  exited
prod 11:  exited
con 0:   29.
con 0:   exited
prod 10:  exited
任何帮助都将不胜感激

如果有人感兴趣,完整代码(很长,但我想大部分相关部分已经在我发布的方法中):

#包括
#包括
#包括
#包括
#包括
/*
*定义共享队列的大小和大小的常量
*生产商和消费者应该完成的全部工作
*/
#定义队列大小5
#定义最大工作时间为30
/*
*这些常量指定生产者和生产者在CPU上的工作量
*消费者在处理商品时所做的。他们还定义了每个商品的长度
*生成项目时阻塞。工作和阻塞已实现
*使用msleep()例程阻止的do_work()例程
*至少在指定的毫秒数内。
*/
#定义处理器25
#定义第10块
#定义用户CPU 25
#定义消费单元块10
/*****************************************************
*共享队列相关结构和例程*
*****************************************************/
类型定义结构{
int buf[QUEUESIZE];/*队列内容数组,作为循环队列管理*/
int head;/*队列头的索引*/
int tail;/*队列尾部的索引,下一个空槽*/
int full;/*队列已满时设置的标志*/
int empty;/*队列为空时设置的标志*/
pthread_mutex_t*mutex;/*mutex保护此队列的数据*/
pthread_cond_t*notFull;/*用于生产者等待生产空间*/
pthread_cond_t*notEmpty;/*用于消费者等待消费*/
}排队;
/*
*创建在所有生产者和消费者之间共享的队列
*/
队列*queueInit(无效)
{
队列*q;
/*
*分配保存所有队列信息的结构
*/
q=(队列*)malloc(sizeof(队列));
如果(q==NULL)返回(NULL);
/*
*初始化状态变量。请参阅队列的定义
*结构的定义。
*/
q->空=1;
q->full=0;
q->head=0;
q->tail=0;
/*
*分配并初始化队列互斥
*/
q->mutex=(pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(q->mutex,NULL);
/*
*分配并初始化notFull和notEmpty条件
*变数
*/
q->notFull=(pthread_cond_t*)malloc(sizeof(pthread_cond_t));
pthread_cond_init(q->notFull,NULL);
q->notEmpty=(pthread_cond_t*)malloc(sizeof(pthread_cond_t));
pthread_cond_init(q->notEmpty,NULL);
回报率(q);
}
/*
*删除共享队列,取消分配动态分配的内存
*/
void queueDelete(队列*q)
{
/*
*销毁互斥锁并释放其内存
*/
pthread_mutex_destroy(q->mutex);
自由(q->mutex);
/*
*销毁并取消分配条件变量
*/
pthread_cond_destroy(q->notFull);
免费(q->未满);
pthread_cond_destroy(q->notEmpty);
免费(q->notEmpty);
/*
*取消分配队列结构
*/
免费(q);
}
void queueAdd(队列*q,整数in)
{
/*
*将输入项放入空闲插槽
*/
q->buf[q->tail]=in;
q->tail++;
/*
*如果到达末尾,将tail的值环绕为零
*数组。这实现了队列在
*数组。
*/
如果(q->tail==队列大小)
q->tail=0;
/*
*如果尾部指针等于头部,则enxt为空
*队列中的插槽已被占用,且队列已满
*/
如果(q->tail==q->head)
q->full=1;
/*
*因为我们刚刚向队列中添加了一个元素,所以它肯定不是
*空的。
*/
q->空=0;
返回;
}
void queueRemove(队列*q,整数*out)
{
/*
*将head处的元素复制到输出变量并递增
*移动到下一个元素的头指针。
*/
*out=q->buf[q->head];
q->head++;
/*
*如果索引达到
*这实现了队列在数组中的循环性。
*/
如果(q->head==队列大小)
q->head=0;
/*
*当我们删除一个项目时,如果head赶上tail,那么队列
*是空的。
*/
如果(q->head==q->tail)
q->空=1;
/*
*由于我们取出了一件物品,队列肯定没有满
*/
q->full=0;
返回;
}
/******************************************************
*生产者和消费者结构和惯例*
******************************************************/
/*
*用于传递消费者和生产者的参数结构
*争论。
* 
*q-arg提供指向共享队列的指针。
*
*count-arg是指向此线程的计数器的指针,用于跟踪
*它做了很多工作。
*
*tid-arg提供生产者或消费者的ID号,
*这也是它在线程结构数组中的索引。
* 
*/
类型定义结构{
队列*q;
整数*计数;
国际贸易署;
}个人电脑
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

/*
 * Define constants for how big the shared queue should be and how
 * much total work the produceers and consumers should perform
 */

#define QUEUESIZE 5
#define WORK_MAX 30

/*
 * These constants specify how much CPU bound work the producer and
 * consumer do when processing an item. They also define how long each
 * blocks when producing an item. Work and blocking are implemented
 * int he do_work() routine that uses the msleep() routine to block
 * for at least the specified number of milliseconds.
 */
#define PRODUCER_CPU   25
#define PRODUCER_BLOCK 10
#define CONSUMER_CPU   25
#define CONSUMER_BLOCK 10

/*****************************************************
 *   Shared Queue Related Structures and Routines    *
 *****************************************************/
typedef struct {
  int buf[QUEUESIZE];   /* Array for Queue contents, managed as circular queue */
  int head;             /* Index of the queue head */
  int tail;             /* Index of the queue tail, the next empty slot */  

  int full;             /* Flag set when queue is full  */
  int empty;            /* Flag set when queue is empty */

  pthread_mutex_t *mutex;     /* Mutex protecting this Queue's data */
  pthread_cond_t  *notFull;   /* Used by producers to await room to produce*/
  pthread_cond_t  *notEmpty;  /* Used by consumers to await something to consume*/
} queue;

/*
 * Create the queue shared among all producers and consumers
 */
queue *queueInit (void)
{
  queue *q;

  /*
   * Allocate the structure that holds all queue information
   */
  q = (queue *)malloc (sizeof (queue));
  if (q == NULL) return (NULL);

  /*
   * Initialize the state variables. See the definition of the Queue
   * structure for the definition of each.
   */
  q->empty = 1;  
  q->full  = 0;   

  q->head  = 0;   
  q->tail  = 0;   

  /*
   * Allocate and initialize the queue mutex
   */
  q->mutex = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t));
  pthread_mutex_init (q->mutex, NULL);

  /*
   * Allocate and initialize the notFull and notEmpty condition
   * variables
   */
  q->notFull = (pthread_cond_t *) malloc (sizeof (pthread_cond_t));
  pthread_cond_init (q->notFull, NULL);

  q->notEmpty = (pthread_cond_t *) malloc (sizeof (pthread_cond_t));
  pthread_cond_init (q->notEmpty, NULL);

  return (q);
}

/*
 * Delete the shared queue, deallocating dynamically allocated memory
 */
void queueDelete (queue *q)
{
  /*
   * Destroy the mutex and deallocate its memory
   */
  pthread_mutex_destroy (q->mutex);
  free (q->mutex);
  
  /*
   * Destroy and deallocate the condition variables
   */
  pthread_cond_destroy (q->notFull);
  free (q->notFull);

  pthread_cond_destroy (q->notEmpty);
  free (q->notEmpty);

  /*
   * Deallocate the queue structure
   */
  free (q);
}

void queueAdd (queue *q, int in)
{
  
  /*
   * Put the input item into the free slot
   */
  q->buf[q->tail] = in;
  q->tail++;

  /*
   * wrap the value of tail around to zero if we reached the end of
   * the array. This implements the circularity of the queue inthe
   * array.
   */
  if (q->tail == QUEUESIZE)
    q->tail = 0;

  /*
   * If the tail pointer is equal to the head, then the enxt empty
   * slot in the queue is occupied and the queue is FULL
   */
  if (q->tail == q->head)
    q->full = 1;

  /*
   * Since we just added an element to the queue, it is certainly not
   * empty.
   */
  q->empty = 0;

  return;
}

void queueRemove (queue *q, int *out)
{
  /*
   * Copy the element at head into the output variable and increment
   * the head pointer to move to the next element.
   */
  *out = q->buf[q->head];
  q->head++;

  /*
   * Wrap the index around to zero if it reached the size of the
   * array. This implements the circualrity of the queue int he array.
   */
  if (q->head == QUEUESIZE)
    q->head = 0;

  /*
   * If head catches up to tail as we delete an item, then the queue
   * is empty.
   */
  if (q->head == q->tail)
    q->empty = 1;

  /*
   * since we took an item out, the queue is certainly not full
   */
  q->full = 0;

  return;
}

/******************************************************
 *   Producer and Consumer Structures and Routines    *
 ******************************************************/
/*
 * Argument struct used to pass consumers and producers thier
 * arguments.  
 * 
 * q     - arg provides a pointer to the shared queue. 
 *
 * count - arg is a pointer to a counter for this thread to track how
 *         much work it did.
 *
 * tid   - arg provides the ID number of the producer or consumer, 
 *         whichis also its index into the array of thread structures.
 * 
 */
typedef struct {
  queue *q;       
  int   *count;   
  int    tid;
} pcdata;

int memory_access_area[100000];


/*
 * Sleep for a specified number of milliseconds. We use this to
 * simulate I/O, since it will block the process. Different lengths fo
 * sleep simulate interaction with different devices.
 */
void msleep(unsigned int milli_seconds)
{
  struct timespec req = {0}; /* init struct contents to zero */
  time_t          seconds;

  /*
   * Convert number of milliseconds input to seconds and residual
   * milliseconds to handle the cse where input is more than one
   * thousand milliseconds.
   */
  seconds        = (milli_seconds/1000);
  milli_seconds  = milli_seconds - (seconds * 1000);

  /*
   * Fill in the time_spec's seconds and nanoseconds fields. Note we
   * multiply millisconds by 10^6 to convert to nanoseconds.
   */
  req.tv_sec  = seconds;
  req.tv_nsec = milli_seconds * 1000000L;

  /*
   * Sleep for the required period. The first parameter specifies how
   * long. In theory this thread can be awakened before the period is
   * over, perhaps by a signal. If so the timespec specified by the
   * second argument is filled in with how much time int he original
   * request is left. We use the same one. If this happens, we just
   * call nanosleep again to sleep for what remains of the origianl
   * request.
   */
  while(nanosleep(&req, &req)==-1) {
    printf("restless\n");
    continue;
  }

}

/*
 * Simulate doing work. 
 */
void do_work(int cpu_iterations, int blocking_time)
{
  int i;
  int j;
  int local_var;

  local_var = 0;
  for (j = 0; j < cpu_iterations; j++ ) {
    for (i = 0; i < 1000; i++ ) {
      local_var = memory_access_area[i];
    }
  }
  
  if ( blocking_time > 0 ) {
    msleep(blocking_time);
  }
}

void *producer (void *parg)
{
  queue  *fifo;
  int     item_produced;
  pcdata *mydata;
  int     my_tid;
  int    *total_produced;

  mydata = (pcdata *) parg;

  fifo           = mydata->q;
  total_produced = mydata->count;
  my_tid         = mydata->tid;

  /*
   * Continue producing until the total produced reaches the
   * configured maximum
   */
  while (1) {
      
   
    pthread_mutex_lock(fifo->mutex);
    do_work(PRODUCER_CPU, PRODUCER_BLOCK);
 
    /*
     * If the queue is full, we have no place to put anything we
     * produce, so wait until it is not full.
     */
    while (fifo->full && *total_produced != WORK_MAX) {
      printf ("prod %d:  FULL.\n", my_tid);
      pthread_cond_wait(fifo->notFull, fifo->mutex);
  
    }

    /*
     * Check to see if the total produced by all producers has reached
     * the configured maximum, if so, we can quit.
     */
    if (*total_produced >= WORK_MAX) {
      pthread_mutex_unlock(fifo->mutex);
      break;
    }

    /*
     * OK, so we produce an item. Increment the counter of total
     * widgets produced, and add the new widget ID, its number, to the
     * queue.
     */
    item_produced = (*total_produced)++;
    queueAdd (fifo, item_produced);

    /*
     * Announce the production outside the critical section 
     */
    printf("prod %d:  %d.\n", my_tid, item_produced);
    pthread_cond_signal(fifo->notEmpty);
    pthread_mutex_unlock(fifo->mutex);
  }

  printf("prod %d:  exited\n", my_tid);
  return (NULL);
}

void *consumer (void *carg)
{
  queue  *fifo;
  int     item_consumed;
  pcdata *mydata;
  int     my_tid;
  int    *total_consumed;

  mydata = (pcdata *) carg;

  fifo           = mydata->q;
  total_consumed = mydata->count;
  my_tid         = mydata->tid;

  /*
   * Continue producing until the total consumed by all consumers
   * reaches the configured maximum
   */
  while (1) {
     pthread_mutex_lock(fifo->mutex);
    /*
     * If the queue is empty, there is nothing to do, so wait until it
     * si not empty.
     */
    while (fifo->empty && *total_consumed != WORK_MAX) {
      printf ("con %d:   EMPTY.\n", my_tid);
      pthread_cond_wait(fifo->notEmpty, fifo->mutex);
    }

    /*
     * If total consumption has reached the configured limit, we can
     * stop
     */
    if (*total_consumed >= WORK_MAX) {
       pthread_mutex_unlock(fifo->mutex);
      break;
    }

    /*
     * Remove the next item from the queue. Increment the count of the
     * total consumed. Note that item_consumed is a local copy so this
     * thread can retain a memory of which item it consumed even if
     * others are busy consuming them. 
     */
    queueRemove (fifo, &item_consumed);
    (*total_consumed)++;
    do_work(CONSUMER_CPU,CONSUMER_CPU);

    printf ("con %d:   %d.\n", my_tid, item_consumed);

    pthread_cond_signal(fifo->notFull);
    pthread_mutex_unlock(fifo->mutex);

    
    
    
  }

  printf("con %d:   exited\n", my_tid);
  return (NULL);
}

/***************************************************
 *   Main allocates structures, creates threads,   *
 *   waits to tear down.                           *
 ***************************************************/
int main (int argc, char *argv[])
{
  pthread_t *con;
  int        cons;
  int       *concount;

  queue     *fifo;
  int        i;

  pthread_t *pro;
  int       *procount;
  int        pros;

  pcdata    *thread_args;

  /*
   * Check the number of arguments and determine the numebr of
   * producers and consumers
   */
  if (argc != 3) {
    printf("Usage: producer_consumer number_of_producers number_of_consumers\n");
    exit(0);
  }

  pros = atoi(argv[1]);
  cons = atoi(argv[2]);

  /*
   * Create the shared queue
   */
  fifo = queueInit ();
  if (fifo ==  NULL) {
    fprintf (stderr, "main: Queue Init failed.\n");
    exit (1);
  }

  /*
   * Create a counter tracking how many items were produced, shared
   * among all producers, and one to track how many items were
   * consumed, shared among all consumers.
   */
  procount = (int *) malloc (sizeof (int));
  if (procount == NULL) { 
    fprintf(stderr, "procount allocation failed\n"); 
    exit(1); 
  }
  
  concount = (int *) malloc (sizeof (int));
  if (concount == NULL) { 
    fprintf(stderr, "concount allocation failed\n"); 
    exit(1); 
  }

  /*
   * Create arrays of thread structures, one for each producer and
   * consumer
   */
  pro = (pthread_t *) malloc (sizeof (pthread_t) * pros);
  if (pro == NULL) { 
    fprintf(stderr, "pros\n"); 
    exit(1); 
  }

  con = (pthread_t *) malloc (sizeof (pthread_t) * cons);
  if (con == NULL) { 
    fprintf(stderr, "cons\n"); 
    exit(1); 
  }

  /*
   * Create the specified number of producers
   */
  for (i=0; i<pros; i++){ 
    /*
     * Allocate memory for each producer's arguments
     */
    thread_args = (pcdata *)malloc (sizeof (pcdata));
    if (thread_args == NULL) {
      fprintf (stderr, "main: Thread_Args Init failed.\n");
      exit (1);
    }

    /*
     * Fill them in and then create the producer thread
     */
    thread_args->q     = fifo;
    thread_args->count = procount;
    thread_args->tid   = i;
    pthread_create (&pro[i], NULL, producer, thread_args);
  }

  /*
   * Create the specified number of consumers
   */
  for (i=0; i<cons; i++){
    /*
     * Allocate space for next consumer's args
     */
    thread_args = (pcdata *)malloc (sizeof (pcdata));
    if (thread_args == NULL) {
      fprintf (stderr, "main: Thread_Args Init failed.\n");
      exit (1);
    }

    /*
     * Fill them in and create the thread
     */
    thread_args->q     = fifo;
    thread_args->count = concount;
    thread_args->tid   = i;
    pthread_create (&con[i], NULL, consumer, thread_args);
  }

  /*
   * Wait for the create producer and consumer threads to finish
   * before this original thread will exit. We wait for all the
   * producers before waiting for the consumers, but that is an
   * unimportant detail.
   */
  for (i=0; i<pros; i++)
    pthread_join (pro[i], NULL);
  for (i=0; i<cons; i++)
    pthread_join (con[i], NULL);

  /*
   * Delete the shared fifo, now that we know there are no users of
   * it. Since we are about to exit we could skip this step, but we
   * put it here for neatness' sake.
   */
  queueDelete (fifo);

  return 0;
}
    if (*total_produced >= WORK_MAX) {
      pthread_mutex_unlock(fifo->mutex); //if total work reached then exit
      pthread_cond_signal(fifo->notFull); // other producers should wake up and exit, too
      break;
    }