• 第十二章 并发编程


    第十二章 并发编程

    概述:如果逻辑控制流在时间上重叠,那么他们就是并发的

    应用级并发在其他情况下的应用

    • 访问慢速I/O设备
    • 与人交互
    • 通过推迟工作以降低延迟
    • 服务多个网络客户端
    • 在多核机器上进行并发计算

    12.1基于进程的并发编程

    构造并发程序最简单的方法就是用进程

    常用函数

    • fork
    • exec
    • waitpid

    原理

    在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。

    12.1.1基于进程的并发服务器

    关于服务器需要说明的地方、

    • 要包括一个sigchld处理程序,来回收僵死程序
    • 父子进程必须关闭他们各自的connfd
    • 直到父子进程的connfd都关闭了,到客户端的链接才会终止

    12.1.2关于进程的优劣

    1.优点:防止虚拟存储器被错误覆盖

    2.缺点:开销高,共享状态信息才需要IPC机制

    12.2基于I/O多路复用的并发编程

    使用I/O多路复用的并发编程的原因

    同时响应下面两个请求

    • 网络客户端发起连接请求
    • 用户在键盘上输入命令行

    原理

    就是使用select函数要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。

    12.2.1基于I/O多路复用的并发事件驱动服务器

    事件驱动程序:将逻辑流模型化为状态机。
    状态机:

    • 状态
    • 输入事件
    • 转移

    12.2.2 I/O多路复用技术的优劣

    1.优点

    • 相较基于进程的设计,给了程序员更多的对程序程序的控制
    • 运行在单一进程上下文中,所以每个逻辑流都可以访问该进程的全部地址空间,共享数据容易实现
    • 可以使用GDB调试
    • 高效
      2.缺点
    • 编码复杂
    • 不能充分利用多核处理器

    12.3基于线程的并发编程

    是以上两种方法的混合

    • 线程有内核自动调度
    • 多个线程运行在单一进程的上下文中

    12.3.2posix线程

    void *thread(void *vargp);
    int main()
    {
       pthread_t tid;
       Pthread_create(&tid, NULL, thread, NULL);
       Pthread_join(tid,NULL);
       exit(0);
    }
    
    void *thread(void *vargp)
    {
       printf(“Hello , world
    ”);
       return NULL;
    }
    
    

    12.3.3创建线程

    线程通过调用pthread_create函数来创建其它线程

    #include <pthread.h>
    typedef void *(func)(void *);
    
    int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
    

    12.3.4终止线程

    • 顶层的线程例程返回
    • 调用pthread_exit函数
    • 如果主线程调用,会先等待所有其他对等线程终止,再终止主线程和整个进程,返回值为pthread_return
      某个对等线程调用Unix的exit函数,会终止进程与其相关线程
    • 另一个对等线程通过以当前线程ID作为参数调用pthread_cancle来终止当前线程

    12.4多线程程序中的共享变量

    一个变量是共享的,当且仅当多个线程引用这个变量的某个实例。

    12.5用信号量同步线程

    第一个 CreateSemaphore

    函数功能:创建信号量

    函数原型:

    HANDLE CreateSemaphore(

    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

    LONG lInitialCount,

    LONG lMaximumCount,

    LPCTSTR lpName

    );

    函数说明:

    第一个参数表示安全控制,一般直接传入NULL。

    第二个参数表示初始资源数量。

    第三个参数表示最大并发数量。

    第四个参数表示信号量的名称,传入NULL表示匿名信号量。

    第二个 OpenSemaphore

    函数功能:打开信号量

    函数原型:

    HANDLE OpenSemaphore(

    DWORD dwDesiredAccess,

    BOOL bInheritHandle,

    LPCTSTR lpName

    );

    函数说明:

    • 第一个参数表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS。详细解释可以查看MSDN文档。

    • 第二个参数表示信号量句柄继承性,一般传入TRUE即可。

    • 第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。

    第三个 ReleaseSemaphore

    函数功能:递增信号量的当前资源计数

    函数原型:

    BOOL ReleaseSemaphore(

    HANDLE hSemaphore,

    LONG lReleaseCount,

    LPLONG lpPreviousCount

    );

    函数说明:

    • 第一个参数是信号量的句柄。

    • 第二个参数表示增加个数,必须大于0且不超过最大资源数量。

    • 第三个参数可以用来传出先前的资源计数,设为NULL表示不需要传出。

    由于信号量是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

    12.6使用线程提高并行性

    四类线程不安全函数:

    • 不保护共享变量的函数

    • 保持跨越多个调用的状态的函数

    • 返回指向静态变量的指针的函数

    • 调用线程不安全函数的函数

    可重入性:当函数被多个线程调用,不会引用任何共享数据

    显式可重入:所有函数参数都是传值传递,所有数据引用都是本地自动栈变量

    隐式可重入:显式可重入加上一些参数是引用传递(指向非共享数据的指针)

    竞争:当一个程序的正确性依赖于,一个线程要在另一个线程到达y点之前到达它的控制流中的x点,就会发生竞争

    线程化的程序必须对任何可行的轨迹线都正确工作

    死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。

    死锁不总是可以预测的,而错误常常不可重复。

    学习心得

    这周的内容和操作系统的内容有相似的地方,都是从分析并发执行的程序的状况。操作系统对于进程的调用,
    进程的死锁情况的解决以及更多别的问题讲的更详细。但是本书更注重从代码的角度讲述并发编程的问题,所以通过
    学习这门课,使我从不同的角度了解了操作系统。

    参考文献:

    • 深入理解计算机系统第十二章
    • 20135202闫佳歆同学的博客
    • zhanghaodx082的专栏http://blog.csdn.net/zhanghaodx082/article/details/11932865
  • 相关阅读:
    PostgerSQL 回收表空间,查看占用磁盘大小
    为 Docker 添加阿里云的镜像地址
    Docker 常用命令
    CentOS 7 安装 Docker
    kafka-常用脚本2
    Nginx 端口被占用(0.0.0.0:443 failed (98: Address already in use))
    nginx: [error] open() "/var/run/nginx.pid" failed (2: No such file or directory)
    检查Nginx 配置文件出否有问题
    Python2 安装虚拟环境
    记录 | 程序员技术博客平台推荐和选取
  • 原文地址:https://www.cnblogs.com/5320zhq/p/5022644.html
Copyright © 2020-2023  润新知