65.9K
CodeProject 正在变化。 阅读更多。
Home

避免线程通信中的数据丢失

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.60/5 (5投票s)

2010 年 9 月 7 日

CPOL

5分钟阅读

viewsIcon

27311

避免线程通信中的数据丢失

目录

  • 1 - 问题
  • 2 - 解决方案
  • 3 - 程序
  • 3.1 - 缓冲区
  • 3.2 - 开发
  • 3.2.1 - 创建缓冲区
  • 3.2.2 - 写入缓冲区
  • 3.2.3 - 从缓冲区读取
  • 3.2.4 - 创建节点
  • 3.2.5 - 删除缓冲区
  • 4 - 测试
  • 5 - Makefile

1 - 问题

假设您有一个设备,它会进行读数并通过(例如)套接字将读数发送到计算机。如果在两次读数之间计算机需要进行一些计算,那么当通信速率较高时,可能会在计算机端丢失一些数据。

Socket.jpg

2 - 解决方案

为了解决这种情况,您可以在计算机端创建一个线程,其唯一任务是从套接字中取出数据并将其放入缓冲区,主程序将尽快读取。此外,由于您不知道缓冲区的最佳长度,如果缓冲区可以根据需要增长,那就更好了。

Thread.jpg

3 - 程序

3.1 - 缓冲区

缓冲区是一个环形结构,具有可变数量的节点,每个节点具有以下组成部分:

  • 指向环中下一个节点的指针

  • 指向固定长度单元的指针,用于存储数据项

  • 一个字符,指示单元中的最后一次操作,读取或写入

除了节点之外,还有一个控制结构,称为“Ring”,它具有以下组成部分:

  • 指向最后读取节点的指针

  • 指向最后写入节点的指针

  • 缓冲区中的节点数

  • 单元的大小(以字节为单位)

Ring.jpg

3.2 - 开发

开发的程序包含必要的功能,用于

  • 创建缓冲区
  • 写入缓冲区
  • 从缓冲区读取
  • 为缓冲区添加更多空间
  • 删除缓冲区

3.2.1 - 创建缓冲区

要创建缓冲区,您可以使用以下函数:

void ring_init(ring_struct *ring, int cell_size)
{
    node_struct *fst = NULL, *lst = NULL;
    ring_create_nodes(NODES_ON_INIT, cell_size, &fst, &lst);
    ring->r = ring->w = lst->nxt = fst;
 
    ring->node_num = 
NODES_ON_INIT;
    ring->cell_size = cell_size;
}

它首先分配 `NODES_ON_INIT` 个节点,然后调整环形结构中的指针,使最后一个创建的节点指向第一个节点,最后填充环形结构的其余部分。

3.2.2 - 写入缓冲区

当需要写入缓冲区时,使用以下函数:

int ring_write(ring_struct *ring, void *cell_source)
{
    node_struct *fst = NULL, *lst = NULL;
    if(ring->w->nxt->status == 'r')
    {
        ring->w = ring->w->nxt;
        memmove(ring->w->cell, cell_source, 
ring->cell_size);
        ring->w->status = 'w';
    }
    else
    {
        ring->node_num += NODES_ON_ADD;
        if((NODES_MAX_NUM != 0) && (ring->node_num 
> NODES_MAX_NUM))
            return(-1);
        ring_create_nodes(NODES_ON_ADD, 
ring->cell_size, &fst, &lst);
        lst->nxt = ring->w->nxt;
        ring->w->nxt = fst;
        ring_write(ring, cell_source);
    }
    return(0);
}

在这里,您会遇到两种情况:下一个节点被标记为已读取或已被写入。在前一种情况下,没有问题可以覆盖写入;在后一种情况下,如果您覆盖它,您将丢失数据,因为该节点尚未被读取。

如预期的那样,在前一种情况下,您指向下一个节点,将数据复制到单元中,并将节点标记为已写入。在后一种情况下,还有几个步骤需要采取。为了写入更多数据,您将通过添加一些模式节点来扩展缓冲区。首先,因为缓冲区可以配置为最大大小,所以您需要询问您即将添加的节点是否不会达到该大小。如果没问题,则调用创建节点的函数来添加 `NODES_ON_ADD` 个节点,这些节点以读取标志创建,调整指针,然后再次调用该函数,因为现在下一个节点已被标记为读取。

3.2.3 - 从缓冲区读取

要从缓冲区读取,您可以使用以下函数:

int 
ring_read(ring_struct *ring, void *cell_dest)
{
    if(ring->r->nxt->status == 'r')
        return(0);
    else
    {
        ring->r = ring->r->nxt;
        memmove(cell_dest, ring->r->cell, 
ring->cell_size);
        ring->r->status = 'r';
        return(1);
    }
}

在这里,您再次询问下一个节点是标记为读取还是写入。在前一种情况下,没有可读的数据,因为所有单元都标记为读取。如果标记为已写入,则可以通过复制单元内容、调整指针和将节点标记为已读取来进行读取。

3.2.4 - 创建节点

创建节点这个简单但更复杂的功能。创建它的代码是:

void ring_create_nodes(int n, int cell_size, node_struct **fst, 
node_struct **lst)
{
    int i;
    node_struct *node = NULL, *ptr = NULL;
    for(i = 0; i < n; i++)
    {
        node = (node_struct *) 
malloc(sizeof(node_struct));
        if(!node)
            
ring_error_and_exit((char *) "Error in 'malloc' for 'node'");
        node->status = 'r';
        node->cell = malloc(cell_size);
        if(!node->cell)
            
ring_error_and_exit((char *) "Error in 'malloc' for 'cell'");
        if(!i)
        {
            *lst = node;
            node->nxt = 
NULL;
        }
        else
            node->nxt = 
ptr;
        ptr = node;
    }
    *fst = node;
}

目标是创建有限数量的节点,它们之间相互连接,并与第一个和最后一个节点处于打开状态。为此,您创建一个循环,该循环根据您想要创建的节点数量,首先为节点和单元分配空间,将节点标记为读取,并将其连接到最后一个创建的节点。如果这是第一个创建的节点(它将是队列的最后一个,因为队列向后增长),它将指向 `NULL`,并且参数 `LST`(必须指向最后一个节点)将指向它。最后,参数 `FST`(必须指向队列中的第一个节点)指向最后一个创建的节点。

3.2.5 - 删除缓冲区

当不再需要缓冲区时,您必须删除它。要做到这一点,您可以使用以下代码:

void ring_free(ring_struct *ring)
{
    node_struct *fst = NULL, *del = NULL;
    fst = ring->w;
    while(1)
    {
        del = ring->w;
        ring->w = del->nxt;
        free(del->cell);
        free(del);
        if(ring->w == fst)
            break;
    }
}

您从缓冲区中的任何一个节点开始,记下它,然后遍历缓冲区中的所有节点,释放分配给单元的空间,然后释放分配给节点本身的空间,直到到达第一个节点。

4 - 测试

为了测试程序,开发了一个应用程序,该应用程序创建一个写入数据的线程,主程序读取数据并将其显示在屏幕上。在写入时包含了一个延迟,在读取时包含了更长的延迟。这样缓冲区就会被填满,并且可以测试其增长。还包含了一个最大尺寸,当达到该尺寸时,应用程序将结束。

5 - Makefile

为了构建和测试整个应用程序,创建了一个 makefile 来编译所有模块。该应用程序在 Cygwin 环境下进行了测试,但它也能在任何 Linux 或 Unix 环境下运行。

© . All rights reserved.