apue读书笔记 - 第11章 线程

实例11-1

为在Ubuntu12.04上的运行结果与书中描述的不一致呢?

从pid来看这两个线程属于同一个进程,且线程ID也是指针形式的,Google后得知,书上讲的是以前的LinuxThreads实现,现在linux使用NPTL线程。

参考:Linux 线程模型的比较:LinuxThreads 和 NPTL

习题11.4

在回答该问题之前,我觉得得先弄清楚“互斥量”与“条件变量”之间的关系。因为书上说这两者一般是配套使用的。

考虑如下情况:

子线程B和子线程C都通过同一个条件变量cond等待主线程A发送信号,若判断测试条件为假,则这两个线程都会继续阻塞休眠。但是,判断条件不正确和休眠这之间有个时间窗口,假如当这两个子线程刚判断条件为假后但并未进入阻塞休眠是时,CPU切换到了主线A,线程A使条件变为真了。那么当CPU切换会B、C线程时,线程还是会进入休眠(且错过了判断条件为真的时机,有可能再也无法等到条件为真)。也就是说在线程检查条件变量和进入休眠等待条件改变这两个操作之间存在一个时间窗口。这里存在着竞争。

我们知道互斥量是可以用来解决上面的竞争问题的,所以“条件变量本身是由”互斥量“来保护的。

既然判断和睡眠是由互斥量来保护从而成为一个原子操作,那么其他改变条件的线程就应该以一致的方式修改条件,也就是说其他线程在改变条件状态前也必须首先锁住互斥量。(如果修改操作不是用互斥量来保护的话,那么判断和休眠使用互斥量来保护也就没有意义。因为其他线程还是可以在两个操作的空隙中改变条件。但是如果修改操作也使用互斥量。因为判断和休眠之前先加锁了。那么修改操作就只能等到判断失败和休眠两个操作完成才能进行而不会在这之间修改)

大致的流程如下:

线程B:lock->判断条件(假)->wait(休眠&unlock)->lock->执行相应操作

线程A:lock->使条件为真->unlock->发送信号

看个具体的例子

#include <stdio.h>
#include <pthread.h>

unsigned int test_signal = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  cond = PTHREAD_COND_INITIALIZER;

void *thread1(void *arg)
{
    pthread_mutex_lock(&mutex);            
    while (1000 != test_signal)      
    {
        pthread_cond_wait(&cond,&mutex);
    }
    pthread_mutex_unlock(&mutex);

    printf("This is thread 1\n");
    pthread_exit((void *)0);
}

void *thread2(void *arg)
{
    pthread_mutex_lock(&mutex);
    while (1000 != test_signal)
    {
        pthread_cond_wait(&cond,&mutex);
    }
    pthread_mutex_unlock(&mutex);

    printf("This is thread 2\n");
    pthread_exit((void *)0);
}

int main(void)
{
    pthread_t tid1, tid2;

    pthread_create(&tid1, NULL, thread1, (void *)0);
    pthread_create(&tid2, NULL, thread2, (void *)0);

    while (1)
    {       
        pthread_mutex_lock(&mutex);          
        test_signal++;
        pthread_mutex_unlock(&mutex);
        if (1000 == test_signal)
        {
            pthread_cond_broadcast(&cond);
            break;
        }
    }

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    exit(0);
}

OK,现在回到问题本身,这两种步骤其实都是可以的 但是都存在一些不足。

a步骤:

(1)对互斥量加锁(pthread_mutex_lock)
(2)改变互斥量保护的条件。(对应上面的例子就是在主线程中的test_signal++
操作)
(3)向等待条件的线程发送信号(pthread_cond_broadcast)
(4)对互斥量解锁(pthread_mutex_unlock)

也就是主线程在发送条件成立信号在解锁前。那么也就是主线程发送信号后还是持有锁的,当子线程收到信号后会结束休眠但是前面说过pthread_cond_wait返回时会再次获得锁,但是主线程还并未释放锁,所以会造成子线程收到信号开始运行但立即被阻塞。

b步骤:

(1)对互斥量加锁(pthread_mutex_lock)
(2)改变互斥量保护的条件。(对应上面的例子就是在主线程中的 i++ 操作)
(3)对互斥量解锁(pthread_mutex_unlock)
(4)向等待条件的线程发送信号(pthread_cond_broadcast)

主线程在释放锁后才发送信号。上面的例子就是这么做的。但是这也存在一个问题但释放锁后,其他线程很可能会在发送信号之前获得锁并修改 test_signal 导致条件再次不成立,但是主线程却并不知情,导致仍会发送信号给子线程。子线程认为条件满足从休眠中醒来开始运行,但此时条件是不满足的。
所以在上面的例子中

while (1000 != test_signal)
{
    pthread_cond_wait(&cond,&mutex);
}

让子线程醒来后再次判断条件是否成立还是很有必要的,这样就可以避免了上面的问题。

时间: 2024-10-11 04:28:29

apue读书笔记 - 第11章 线程的相关文章

《Unix环境高级编程》读书笔记 第11章-线程

1. 引言 了解如何使用多个控制线程在单进程环境中执行多个任务. 不管在什么情况下,只要单个资源需要在多个用户键共享,就必须处理一致性问题. 2. 线程概念 典型的Unix进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事情. 多线程带来的好处: 通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码.每个线程在进行事件处理时可以采用同步编程模式. 多个进程必须使用操作系统提供的复制机制才能实现内存和文件描述符的共享.而多个线程自动地可以访问相同的存储空间和文件描述符. 有些

《浪潮之巅》读书笔记——第11章 摩托罗拉

第11章 摩托罗拉  公司发展    1928创立 原名高尔文制造公司    基因      注重技术和品质    1947 改名摩托罗拉    2011 拆分为摩托罗拉移动和摩托罗拉解决方案两个独立上市公司    2011 Google收购摩托罗拉移动 获得大量专利 扭转了苹果和微软的专利战  产品    最早是做汽车收音机 Motorola是其品牌    给军方做便携式无线通信工具 步话机二战前    模拟无线通信的老大(无线双工霸主)    1946 汽车电话    1958 汽车对讲机 

C++ primer plus读书笔记——第11章 使用类

第11章 使用类 1. 运算符重载是一种形式的C++多态. 2. 不要返回指向局部变量或临时对象的引用.函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据. 3. 运算符重载的格式如下: operatorop(argument-list); 可以使用函数表示法或运算符表示法来使用它. total = coding + fixing; total = coding.operator+(fixing); 4. 运算符重载的限制: 1)  重载后的运算符必须至少有一个操作数是用户定义的类

《APUE》读书笔记第十一章-线程

本章主要介绍了线程,了解如何使用多线程在单进程环境中来执行多任务.由于多个线程共享其进程空间,所以必须采用同步的机制来保护数据的一致性. 一.线程的概念 典型的Unix系统都可以看成只有一个控制线程,一个进程在同一时刻只能做一件事.但有了多线程,我们可以设计成在同一时刻进程能做不止一件事,每个线程处理各自独立的任务.进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本.程序的全局内存和堆内存.栈以及文件描述符. 二.线程标识 #include <pthread.h> int pth

APUE读书笔记-第三章 文件I/O

今天看得挺快的,一下子就把第二章看完了,不过第二章也确实看得不仔细,这一章其实在程序设计中还是非常重要的,因为这一章的内容决定了程序的可移植性. 好了,回到这一章的主题文件I/O. 3.2节主要对文件描述符的概念进行了简单的介绍.根据APUE:文件描述符是一个非负整数.当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符.我也简单地翻了一下LKD和<深入理解linux内核>,其中对于文件描述符的讲解不是很多,所以对于文件描述符也谈不出来太深入理解,各大家还是分享一篇blog吧.

APUE读书笔记-第四章 文件和目录

到第四章了,不知什么时候才能把这本书看完,耽误的时间太多了. 第四章是在第三章的基础上,主要描述文件系统的其他性质和文件的性质. 4.2 stat.fstat.fstatat.lstat函数 首先来看看这四个函数的原型: #include <sys/stat.h> ///usr/include/x86_64-linux-gnu/sys/ int stat (const char *__restrict __file, struct stat *__restrict __buf) int fst

apue读书笔记-第14章 高级IO

多路I/O转接 与select函数不同,poll不是为每个状态(可读性.可写性和异常状态)构造一个描述符集,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号以及其所关心的状态 readv和writev函数 作用:在一次函数调用中读.写多个非连续缓存区 总结:应当用尽量少的系统调用次数来完成任务.如果只写少量的数据,会发现自己复制数据然后使用一次write会比用writev更合算.但也可能发现,这样获得的性能提升并不值得,因为管理中间缓冲区会增加程序复杂度. readn和write

APUE读书笔记-第六章 系统数据文件和信息

昨天看完了,今天来看看第六章.感觉第六章的内容不是非常重要.简单看看吧 6.2 口令文件 口令文件其实就是/etc文件夹下的passwd文件,但处于安全性的考虑,我们无法直接读取它.就是通过直接限制权限的方式对其进行保护,passwd文件具体权限如下: -rw-r--r-- 1 root root 可以看到只有root用户具有读写权限,与root同组的用户与其他用户仅具有读权限. 不过为了解决以上问题,Linux中给出了一系列数据结构与函数帮助我们操纵口令文件,首先是关键数据结构,定义位于/in

APUE读书笔记-第五章 标准I/O库

今天草草的把第四章结了,后面的内容分析的也不是很详细,就连书中的例子都没有怎么实验,还是等以后有机会吧. 从5.3节开始研究起吧,这一节主要谈了一个进程预定义的3个流,分别是标准输入.标准输出和标准错误,通过stdin.stdout.stderr引用.这里要和进程中的文件描述符STDIN_FILENO.STDOUT_FILENO.STDERR_FILENO相区分. /* Standard streams. */ extern struct _IO_FILE *stdin; /* Standard