设为首页 收藏本站
查看: 1449|回复: 0

[经验分享] Linux POSIX多线程编程遇到的线程同步问题以及缓冲区问题

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-4-19 20:50:43 | 显示全部楼层 |阅读模式
Linux POSIX多线程编程遇到的线程同步问题以及缓冲区问题
我写了一个小程序【代码随后贴上】,作用是主线程从一个文件里不断的读取π的值【文件在博文附件里】,并写入大小为10的数组,子线程从这个数组里取得字符并且输出。因为刚开始学多线程,就把信号量和互斥量全用上了,结果很悲哀,死锁了。于是听取群里的朋友的意见,选用经典的  生产者/消费者问题 算法,只使用了三个信号量,终于搞定了。


#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
void *thread_function(void *arg);
int worksize=10;
//char workarea[worksize];
char workarea[10];
sem_t sem;//临界区互斥
sem_t full;//待输出字符个数
sem_t empty;//空闲空间个数
int in=0,out=0;
int main()
{
    int res;
    FILE *fp;
    int ch;
    pthread_t a_thread;
    void *thread_result;
/////////semaphore init////////
    res=sem_init(&sem,0,1);
    if(res!=0)
        {perror("error:");exit(1);}
    res=sem_init(&full,0,0);
    if(res!=0)
        {perror("error:");exit(1);}
    res=sem_init(&empty,0,worksize);
    if(res!=0)
        {perror("error:");exit(1);}
/////////creat thread///////////
    res=pthread_create(&a_thread,NULL,thread_function,NULL);
    if(res!=0)
        {perror("error:");exit(1);}
/////////open file/////////////
    if((fp=fopen("/home/mirage/Desktop/program/pie.txt","r"))==NULL)
        {perror("error:");exit(1);}
///////////producer-read from file////////////
    while(1)
    {
        sem_wait(&empty);
        sem_wait(&sem);     //critical section
            if((ch=fgetc(fp))==EOF)
                break;
            workarea[in]=ch;
            in=(in+1)%worksize;//
        sem_post(&sem);    //no critical section
        sem_post(&full);
    }//while
    sem_destroy(&sem);
    sem_destroy(&empty);
    sem_destroy(&full);
    fclose(fp);
    exit(0);
}//main
///////////consumer-output to terminal////////////
void *thread_function(void *arg)
{
    printf("pie=:3.\n");
    while(1)
    {
        sem_wait(&full);
        sem_wait(&sem);     //critical section
            sleep(1);        //注意问题!!
            printf("%c",workarea[out]);
            out=(out+1)%worksize;
        sem_post(&sem);     //no critical section
        sem_post(&empty);
    }//while
}
   但是随之而来的问题是,输出太快,那个π文件里面的数字有限,一瞬间就输出完了,哗哗的,我想让它慢点,于是在临界区加了sleep(1),问题出现了,程序输出完pie=:3.之后就无限等待,我一开始以为逻辑问题,以为又死锁了呢,但是这就是经典的算法思路不会出问题,而且不加sleep就正常,怎么睡了一秒就不正常了呢。怎么回事?
   我把问题发到了linuxquestion.org上面,让老外高手看看(其实是我太渣了),然后一人回复说,

wKiom1NQ6E_CUEwWAADhAmWCey4911.jpg
   我瞬间明白了是缓冲区问题,也就是说我的子线程输出循环代码块每次都会写一个字符,但不是向终端显示器,而是向缓冲区,只要缓冲区不满,就没有屏幕输出,原来没加sleep时,会迅速填满缓冲区然后输出到屏幕,现在有了sleep,每秒钟向缓冲区写一个字符,缓冲区那么大,我要等好久才能看到缓冲区满了之后的输出。
那么缓冲区到底有多大,这就牵扯到Linux缓冲区概念问题。下面细讲。
标准I/O提供了3种类型的缓冲区。全缓冲、行缓冲、无缓冲。
(1)完全缓冲。标准C库函数对文件的操作一般是完全缓冲。只有当I/O缓冲区被填满(写操作)或者为空(读操作)时,内核才会进行真正的I/O操作。内核一般在对第一个流进行第一次I/O操作时调用malloc()一类的函数分配缓冲区。
(2)行缓冲。当文件实际是一个终端时,一般使用行缓冲。采用行缓冲后,只有遇到换行符时才进行真正的I/O操作。注意的是,由于I/O库中的缓冲区大小有限,因此当一行很长时有可能在没有遇到换行符时就已经进行I/O操作了。
(3)无缓冲。一般标准错误都是无缓冲的,输出的任何信息都会立即反应到文件中。
只有stdio(fread/fwrite/fgets)有行缓冲跟全缓冲的概念,read/write(底层系统调用)并没有行缓冲,全缓冲的概念。对于stdio来说,终端是行缓冲,当终端充定向到文件的时候就变为全缓冲。
我们先看看他们有多大,
wKiom1NQ7CawsiZvAAA-l1vwp3Q620.jpg
可见,全缓冲8192字节。
   至于行缓冲区,我也没找到从哪个文件查看,根据参考资料,行缓冲区1024字节。也就是说,我当时是向终端输出,用的行缓冲,一秒钟写入一个,1024秒后行缓冲区满了,屏幕输出,我要等将近20分钟。。。。
下面做几个测试,验证以上的分析:

基于上面的代码,只更改void *thread_function(void *arg)函数部分。

一、向终端输出,使用行缓冲,如果遇到回车符,缓冲区向屏幕输出。


void *thread_function(void *arg)
{
    int ent=0;               //计数变量
    printf("pie=:3.\n");
    while(1)
    {
        sem_wait(&full);
        sem_wait(&sem);     //critical
            usleep(20000);  //换用usleep,单位毫秒,更精确
            printf("%c",workarea[out]);
            ent++;           //每向行缓冲区写入一个字符,加一
            if(ent==10)
                {printf("\n");ent=0;} //向行缓冲区写入10个字符后,写入回车符
            out=(out+1)%worksize;
        sem_post(&sem);     //no critical
        sem_post(&empty);
    }//while
}

其结果是这样的:
wKioL1NQ7zLiqve5AABUcTDpQU8259.jpg
   因为我换用了usleep函数,每20000毫秒写入缓冲区一次,速度还是很合适的,你会看到每写入10个字符,遇到一个换行符之后,行缓冲区把这10个字符输出到屏幕一次,所以数据是一行一行出现。如此循环往复。
二、证明行缓冲区大小为1024字节。
我们改下代码


void *thread_function(void *arg)
{
    int ent=0;               //计数变量
    printf("pie=:3.\n");
    while(1)
    {
        sem_wait(&full);
        sem_wait(&sem);     //critical
            usleep(20000);  //换用usleep,单位毫秒,更精确
            printf("%c",workarea[out]);
            ent++;           //每向行缓冲区写入一个字符,加一
            if(ent==1024)
                {printf(" ent==1024 ");ent=0;}
             //写入缓冲区1024个字符后,我们输出一句话
            out=(out+1)%worksize;
        sem_post(&sem);     //no critical
        sem_post(&empty);
    }//while
}
结果是这样的:
wKioL1NQ8uDSNdXBAAHiLIVvUJk436.jpg
   也就是说,当我们的计数变量数到1024时,表示向缓冲区写入了1024个字符,此时缓冲区正好向屏幕输出,也就是写了1024个字符后行缓冲区满了。so,行缓冲区大小==1024.【20000毫秒有点长,要写入1024个字符费点时间,你可以把时间间距改小点】
三、采用fflush(stdout)令程序立刻输出缓冲区的内容,不用等到它满了。


void *thread_function(void *arg)
{
    int ent=0;               //计数变量
    printf("pie=:3.\n");
    while(1)
    {
        sem_wait(&full);
        sem_wait(&sem);     //critical
            usleep(20000);  //换用usleep,单位毫秒,更精确
            printf("%c",workarea[out]);
            ent++;           //每向行缓冲区写入一个字符,加一
            if(ent==512)
                {fflush(stdout);printf(" ent==512 ");ent=0;}
             //写入缓冲区512个字符后,我们输出一句话
            out=(out+1)%worksize;
        sem_post(&sem);     //no critical
        sem_post(&empty);
    }//while
}
结果是这样的:
wKioL1NQ9MvTuOEYAAD_z4Yu4XA713.jpg
ent==512说明,行缓冲区只写入了512个字符,但我们用fflush(stdout);刷新行缓冲区,使得里面的结果立即输出。所以虽然只写入了512个字符,但是行缓冲区依然向屏幕输出了。
四、让程序每写入一个字符就输出一个字符
void *thread_function(void *arg)
{
    printf("pie=:3.\n");
    while(1)
    {
        sem_wait(&full);
        sem_wait(&sem);     //critical
            usleep(20000);  //换用usleep,单位毫秒,更精确
            printf("%c",workarea[out]);
            fflush(stdout);
            out=(out+1)%worksize;
        sem_post(&sem);     //no critical
        sem_post(&empty);
    }//while
}
结果是这样的:
wKioL1NQ9kCilSVgAABCB4axAnA314.jpg
   每向缓冲区写入一个字符,用fflush让缓冲区向屏幕输出一次,看到的结果是,字符一个一个接连输出到屏幕上。
最后我的程序写成下面这样就是一个字符接一个字符的输出,每输出10个字符换一行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void *thread_function(void *arg)
{
    int ent=0;
    printf("pie=:3.\n");
    while(1)
    {
        sem_wait(&full);
        sem_wait(&sem);     //critical section
            usleep(80000);
            printf("%c",workarea[out]);
            fflush(stdout);
            ent++;
            if(ent==10)
                {printf("\n");ent=0;}
            out=(out+1)%worksize;
        sem_post(&sem);     //no critical section
        sem_post(&empty);
    }//while
}

   关于无缓冲以及重定向到文件里(使用全缓冲)的情形,我就不写了,差不多是一样的。

   累死我了,当我知道是缓冲区导致我等待很长时间时,我还是不能明白等了半天为什么还不输出。就去查了资料,做了测试。终于彻底搞懂了。花了一个半小时将代码整理成博客贴出来,也是为自己留个笔记,同时也让为此而迷惑的朋友看个例子。
   我本人也是多线程编程初学者,水平很渣,这篇博文内容在高手看来不值一提,根本用不着这么费劲的讲解,但我想将这个问题搞得明白点、具体点,于是才写了这么多。
   其实问题的起因是多线程。而有的朋友只想了解缓冲区知识,那么可以忽略我的多线程编程。
   其实这个程序还有很多问题,比如我没有在主线程里终止子线程,而是等到主线程退了之后让子线程自己消亡。以后,我还要加入随时暂停/开始的功能,当然用Ctrl+Z挂起进程是系统自带的方法,不过我想用信号来做。
   可能写博文过程中会有些许笔误,还望读者指正。


π值文件(只包括小数部分).rar

16.21 KB, 下载次数: 0


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-17804-1-1.html 上篇帖子: linux环境下用vi编辑器的常用命令 下篇帖子: 关于iptables filter INPUT 链 默认拒绝的设置经验。 多线程 缓冲区 Linux
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表