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






3.60/5 (5投票s)
避免线程通信中的数据丢失
目录
- 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 - 问题
假设您有一个设备,它会进行读数并通过(例如)套接字将读数发送到计算机。如果在两次读数之间计算机需要进行一些计算,那么当通信速率较高时,可能会在计算机端丢失一些数据。
2 - 解决方案
为了解决这种情况,您可以在计算机端创建一个线程,其唯一任务是从套接字中取出数据并将其放入缓冲区,主程序将尽快读取。此外,由于您不知道缓冲区的最佳长度,如果缓冲区可以根据需要增长,那就更好了。
3 - 程序
3.1 - 缓冲区
缓冲区是一个环形结构,具有可变数量的节点,每个节点具有以下组成部分:
-
指向环中下一个节点的指针
-
指向固定长度单元的指针,用于存储数据项
-
一个字符,指示单元中的最后一次操作,读取或写入
除了节点之外,还有一个控制结构,称为“Ring”,它具有以下组成部分:
-
指向最后读取节点的指针
-
指向最后写入节点的指针
-
缓冲区中的节点数
-
单元的大小(以字节为单位)
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 环境下运行。