在电信系统CDR处理中,有大量的原始话单数据需要被读取、转换后再写入新的文件。如果使用C语言实现,一个复杂的读写转换处理过程可以分为以下几步:
众所周知,独立的计算机系统的瓶颈一般在于IO,而且写入操作比读取操作耗费的时间多很多。即使是一个非常复杂的文件处理,上述第4步的耗时应该与其它几步的总耗时相当,甚至还要超出。这就意味着,单线程执行下,程序在进行第4步处理的时候,其它几步必须进行等待,但有些等待其实是没有必要的。仔细分析,与第4步处理存在资源共享关系的只有第3步,它们会共享一个字符串数组,而其它第1、2、5步,与之没有关系。因此,在写入目标文件的同时,销毁数据结构、读取下一块数据,以及建立该新块的数据结构,是完全可以在另一个线程执行的,从而达到优化的目的。
多线程优化的思路:主线程负责源文件的读取、数据结构的建立、数据结构到字符串数组的输出,以及数据结构的销毁,副线程只负责字符串数组写入到目标文件。两个线程之间的同步关键在于字符串数组的控制权:主线程输出到字符串数组完毕,副线程才能开始工作,副线程写入目标文件完毕,主线程才能将下一块的数据结构输出到该字符串数组。
于是,具体实现上,需要设置两个信号灯,一个是read_ok,表示主线程的读取转换操作完毕,副线程可工作,另一个是write_ok,表示副线程的写入文件操作完毕,主线程可工作。同时,主线程执行输出到字符串数组前需要等待write_ok的信号,副线程工作前需要等待read_ok的信号。
这个DEMO程序模拟了多线程读写处理和单线程读写处理的比较:
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <time.h> sem_t read_ok; sem_t write_ok; void write() { while ( 1 ) { sem_wait(&read_ok); printf("/t/t/t/twriting/n"); sleep(10); printf("/t/t/t/t<== notify main thread start exporting/n"); sem_post(&write_ok); } } int main() { time_t time1 = time(NULL); printf("/*** MULTI THREAD DEMO ***//n"); pthread_t thread_write; sem_init(&read_ok, 0, 0); sem_init(&write_ok, 0, 1); pthread_create(&thread_write, NULL, (void *)write, NULL); int i = 0; for ( i = 0; i < 3; i++ ) { printf("reading/n"); sleep(2); printf("establishing data structure/n"); sleep(2); sem_wait(&write_ok); printf("exporting data to array/n"); sleep(2); printf("notify writing thread start working ==>/n"); sem_post(&read_ok); printf("destroying data structure/n"); sleep(2); } sem_wait(&write_ok); time_t time2 = time(NULL); printf("well done, time spent %d seconds/n", time2 - time1); time1 = time(NULL); printf("/n/n/*** SINGLE THREAD DEMO ***//n"); for ( i = 0; i < 3; i++ ) { printf("reading/n"); sleep(2); printf("establishing data structure/n"); sleep(2); printf("exporting data to array/n"); sleep(2); printf("/t/t/t/twriting/n"); sleep(10); printf("destroying data structure/n"); sleep(2); } time2 = time(NULL); printf("well done, time spent %d seconds/n", time2 - time1); }
命名为multi_thread_read_write_demo.c,由于需要多线程库文件,编译命令如下:
gcc -o multi_thread_read_write_demo multi_thread_read_write_demo.c –lpthread
/*** MULTI THREAD DEMO ***/
reading
establishing data structure
exporting data to array
notify writing thread start working ==>
destroying data structure
writing
reading
establishing data structure
<== notify main thread start exporting
exporting data to array
notify writing thread start working ==>
destroying data structure
writing
reading
establishing data structure
<== notify main thread start exporting
exporting data to array
notify writing thread start working ==>
destroying data structure
writing
<== notify main thread start exporting
well done, time spent 40 seconds
/*** SINGLE THREAD DEMO ***/
reading
establishing data structure
exporting data to array
writing
destroying data structure
reading
establishing data structure
exporting data to array
writing
destroying data structure
reading
establishing data structure
exporting data to array
writing
destroying data structure
well done, time spent 54 seconds
根据短板效应,这样的设计在写入文件的耗时占总耗时的一半左右的时候,效率最高。如果写入文件的耗时所占的比例很高,也就是文件的转换处理相对简单,这样的多线程设计并没有太大的必要。相反,如果写入文件的耗时所占的比例很低,那么就要考虑一下各个转换步骤的效率是否存在问题了,因为正常情况下,很少有慢得过写磁盘的操作。顺便提一下,上一篇文提到的strcat的问题,不改的话,还真的可能比写磁盘还要慢。