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

本章主要介绍了线程,了解如何使用多线程在单进程环境中来执行多任务。由于多个线程共享其进程空间,所以必须采用同步的机制来保护数据的一致性。

一.线程的概念

典型的Unix系统都可以看成只有一个控制线程,一个进程在同一时刻只能做一件事。但有了多线程,我们可以设计成在同一时刻进程能做不止一件事,每个线程处理各自独立的任务。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。

二.线程标识

#include <pthread.h>
int pthread_equal(pthread_t tid1,pthread_t tid2);//若相等则返回非0值,否则返回0

pthread_t pthread_self(void);//返回调用线程的线程ID

三、线程创建、终止与清理

int pthread_create(pthread_t *tid,const pthread_attr_t* attr,
                   void* (*start)(void*),void* arg);
//返回值:若成功则返回0,否则返回错误编号

单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流.

  1. 线程只是从启动例程中返回,返回值是线程的退出码。
  2. 线程可以被同一进程中的其他线程取消。
  3. 线程调用pthread_exit。

但是这两个函数有一个限制,由于它们可以实现为宏,所以必须在线程相同的作用域中以匹配对的形式使用,pthread_cleanup_push的宏定义可包含字符{,在这种情况下对应的匹配字符}就要在pthread_cleanup_pop定义中出现。

int pthread_join(pthread_t tid,void ** ptr);
//若成功则返回0,否则返回错误编号

调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或被取消。ptr将包含返回码。

如果线程采用pthread_self函数使其处于分离状态,pthread_join调用就会失败.返回EINVAL.

void pthread_cancel(pthread_t tid);//成功返回0,否则返回错误编号

线程可以安排它退出时的需要调用的函数,这与进程可以用atexit函数安排进程退出时需要调用的函数类似。线程可以建立多个清理处理程序,处理程序在栈中,也就是说他们的顺序与它们注册的顺序相反。

void pthread_cleanup_push(void (*rtn)(void*),void *arg);
void pthread_cleanup_pop(int execute);

当线程执行以下动作时调用清理函数,调用参数为arg.

  • 调用pthread_exit时
  • 响应取消请求时
  • 用非零execute参数调用pthread_cleanup_pop时

如果execute参数置为0,清理函数将不被调用。

四.线程同步

当多个线程共享相同内存时,为了保持数据共享的一致性,我们采取线程同步的机制。线程同步有四种方法:

1.互斥量

     可以通过使用pthread的互斥接口保护数据,确保同一时间只有一个线程访问数据.互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住的状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0.否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY.

2.读写锁

    读写锁与互斥锁类似,不过读写锁允许更高的并行性.读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

当读写锁时写加锁状态时,在这个锁被解锁前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞到所有的线程释放读锁。

读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockaddr_t* attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
//成功时返回0,否则返回错误编号

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

3.条件变量

   条件变量给多个线程提供了一个会合的场所,条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

条件本身是由互斥量保护的。线程在改变条件状态时必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁住互斥量以后才能计算条件。

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex_t *mutex,
                          struct timespec* timeout);

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
//成功返回0,失败则返回错误编号
#include <pthread.h>struct msg* workq;
pthread_cond_t qready=PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock=PTHREAD_MUTEX_INITIALIZER;

void process_msg(void){
    struct msg* mp;
    for(;;){
         pthread_mutex_lock(&qlock);
         while(qlock==NULL){
                pthread_cond_wait(&qready,&qlock);
        }

        mp=workq;
        workq=mp->m_next;
        pthread_mutex_unlock(&qlock);
    }
}

void enqueue_msg(struct msg* mp){
    pthread_mutex_lock(&qlock);
    mp->m_next=workq;
    workq=mp;
    pthread_mutex_unlock(&qlock);
    pthread_cond_signal(&qready);
}
时间: 2024-10-27 18:26:32

《APUE》读书笔记第十一章-线程的相关文章

apue读书笔记 - 第11章 线程

实例11-1 为在Ubuntu12.04上的运行结果与书中描述的不一致呢? 从pid来看这两个线程属于同一个进程,且线程ID也是指针形式的,Google后得知,书上讲的是以前的LinuxThreads实现,现在linux使用NPTL线程. 参考:Linux 线程模型的比较:LinuxThreads 和 NPTL 习题11.4 在回答该问题之前,我觉得得先弄清楚"互斥量"与"条件变量"之间的关系.因为书上说这两者一般是配套使用的. 考虑如下情况: 子线程B和子线程C都

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

Windows核心编程读书笔记-第六章线程基础

1.相较于线程,进程所使用的系统资源更多.其原因在于地址空间.为一个进程创建一个虚拟的地址空间需要大量系统资源.线程只有一个内核对象和一个栈. 2.线程的入口函数 DWORD WINAPI ThreadFunc(PVOID pvParam){ DWORD dwResult = 0; ... return(dwResult); } 线程函数的几点说明 线程函数可以任意命名. 线程函数只有一个参数,而其意义由我们(而非操作系统)来定义.因此,我们不必担心ANSI/Unicode问题. 线程函数必须返

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

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

java并发的艺术-读书笔记-第九章线程池

使用线程池的好处: 1.降低资源消耗:减少了线程创建和销毁的资源消耗 2.提高响应速度,当任务到达时,线程可以不尽兴创建直接处理 3.提高线程的可管理性.使用线程池可以对线程进行统一的管理,监控,使用. 线程池的源码分析: public void execute(Runnable command){ if(command==null){ throw new NullPointerException(); } //如果执行线程数小于基本线程,则创建线程,并执行任务 if(poolsize>=cor