多线程编程的概念一

多线程编程的概念

理解Linux线程

相对于Unix操作系统40多年的光辉历史,线程算是出现得比较晚的。

在20世纪90年代线程才慢慢流行起来,而POSIX threads标准的确立已经是1995年的事情了。

Unix原本是不支持线程的,线程概念的引入给Unix家族带来了一些麻烦,很多函数都不是线程安全(thread-safe)的,需要重新定义,信号机制在线程加入以后也变得更加复杂了。

在单核CPU时代,多线程的需求并没有那么强烈,但是随着时间的流逝,事情发生了变化。2005年3月,Herb Sutter在Dobb’s Journal上发表了《The Free Lunch is over:A Fundamental Turn Toward Concurrency in Software》一文

文章分析处理器厂商改善CPU性能的传统方法,如提升时钟速度和指令吞吐量,基本已经走到了尽头,处理器开始向超线程和多核架构靠拢,多核的时代已然来临

为了让代码运行得更快,单纯地依赖更快的硬件已经无法满足要求。

程序员需要编写并发代码,以便充分发挥多核处理器的强大功能,并且使程序的性能得到提升。

线程与进程

在Linux下,程序或可执行文件是一个静态的实体,它只是一组指令的集合,没有执行的含义。

进程是一个动态的实体,有自己的生命周期。

线程是操作系统进程调度器可以调度的最小执行单元。

进程之间,彼此的地址空间是独立的,但线程会共享内存地址空间

同一个进程的多个线程共享一份全局内存区域,包括初始化数据段、未初始化数据段和动态分配的堆内存段——共享变量,也称为共享资源

  • 有了进程,为什么还要多线程?
  • 多线程编程有哪些优点?
  • 多线程编程主要用在什么地方?
  • SMP、NUMA、MPP
  • 多核、4核8线程

进程、线程、协程

这种共享给线程带来了很多的优势,但是处理起来也复杂来许多:

·创建线程花费的时间要少于创建进程花费的时间。

·终止线程花费的时间要少于终止进程花费的时间。

·线程之间上下文切换的开销,要小于进程之间的上下文切换。

·线程之间数据的共享比进程之间的共享要简单

下面用一个简单的实验,来比较下创建10万个进程和10万个线程各自的开销。

创建进程的测试程序将会执行如下操作:

1)调用fork函数创建子进程,子进程无实际操作,调用exit函数立刻退出,父进程等待子进程退出。

2)重复执行步骤1,共执行10万次。

创建线程的测试程序则执行如下操作:

1)调用pthread_create创建线程,线程无实际操作;调用pthread_exit函数,立刻退出;主线程调用pthread_join函数等待线程退出。

2)重复执行步骤1,共执行10万次。

线程间的上下文切换,指的是同一个进程里不同线程之间发生的上下文切换。

由于线程原本属于同一个进程,它们会共享地址空间,大量资源共享,切换的代价小于进程之间的切换是自然而然的事情。

线程的弊端

多线程带来优势的同时,也存在一些弊端。

1)多线程的进程,因地址空间的共享让该进程变得更加脆弱多个线程之中,只要有一个线程不够健壮存在bug(如访问了非法地址引发的段错误),就会导致进程内的所有线程一起完蛋。正所谓:

覆巢之下,安有完卵

城门失火,殃及池鱼

相比之下,进程的地址空间互相独立,彼此隔离得更加彻底。

多个进程之间互相协同,一个进程存在bug导致异常退出,不会影响到其他进程。

2)线程模型作为一种并发的编程模型,效率并没有想象的那么高,会出现复杂度高、易出错、难以测试和定位的问题

目前存在的并发编程,基本可以分成两类:

共享状态式

消息传递式

线程模型采用的是第一种。从现在开始,停止幻想,欢迎来到真实的世界。

一个程序员碰到了一个问题,他决定用多线程来解决。现在两个他问题了有。

——关于线程的冷笑话

在真实的场景中,多线程编程是很复杂的。前面所说的多个任务并行不悖,互不依赖,在大多数情况下只是一种美好的幻想。

首先,多个线程之间,存在负载均衡的问题,现实中很难将全部任务等分给每个线程。想象一下,如果存在10个线程,一个线程承担了90%的任务,9个线程承担了10%的任务,整体的效率立刻就降了下来。

  • 提高程序运行效率
  • 模块细分,防止程序阻塞
  • 高并发、多核、服务器
  • 线程池、协程

准备工作

  • Pthread线程库

    • 线程的实现:windows、Linux
    • Pthread库:POSIX标准中的thread API
    • Glibc与LinuxThread
    • Glibc和NPTL
    • Sgetconf GUN_LIBPTHREAD_VERSION

    Linux的API

    对象操作LINUX Pthread API
    线程创建
    退出
    等待
    pthread_create
    pthread_exit
    pthread_join
    互斥锁创建
    销毁
    加锁
    解锁
    pthread_mutex_init
    pthread_mutex_destroy
    pthread_mutex_lock
    pthread_mutex_unlock
    条件变量创建
    销毁
    触发
    广播
    等待
    pthread_cond_init
    pthread_cond_destroy
    pthread_cond_signal
    pthread_cond_broadcast
    pthread_cond_wait
    读写锁创建
    等待
    销毁
    pthread_rwlock_init
    pthread_rwlock_rdlock
    pthread_rwlock_destroy

使用pthread库

  • 安装man手册
    • apt install glibc-doc manpages-posix-dev
  • 程序的编译
    • gcc main.c -lpthread
    • /usr/lib/libpthread.a
  • pthread常用API

在目录下查找文件

find -name libpthread.a

创建一个线程

API接口说明

函数原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

-函数功能:创建一个线程

-参数说明:

-thread:指向线程ID的指针

-attr:线程属性

-start_routine:线程执行实体入口

-arg:传递给线程的参数

-typedef unsigned long int pthread

当pthread_create成功返回时,新创建线程的线程ID会被设置成thread所指向的内存单元

attr参数用于制定各种不同的线程属性

新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递一个以上参数,需要把这些参数放在一个结构体中。

线程的终止

  • 线程终止的三种方式
    • 从start_routine正常return
    • 显示调用pthread_exit
      • 函数原型:void pthread_exit(void *retval);
      • 返回值通过参数retval传递
    • 线程被pthread_cancel取消

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

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

线程pthread_exit与exit的区别

  • 如果进程中的任意线程调用了exit、_Exit或者_exit,终止整个进程。
  • 线程调用pthread_exit,只会结束当前线程,不影响程序的执行。

等待线程的终止

线程分两种

-Joinable

  • PTHREAD_CREATE_JOINABLE
  • 可通过pthread_join等待线程终止
  • 调用pthread_join的线程会阻塞
  • 一个Joinbale线程结束时,资源不会自动释放给系统(堆栈、exit状态等)
  • 当线程终止时,pthread_join会回收该线程资源,然后返回
  • 无pthread_join参与“擦屁股”工作,该线程将变成僵尸线程

-Unjoinable

  • PTHREAD_CREATE_DETACHED
  • 可通过pthread_detach分离一个线程
  • int pthread_detach(pthread_t thread);
  • 线程终止时,资源会自动释放给系统

API接口

  • 函数原型:int pthread_detach(pthread_t thread);
  • 函数功能:将指定线程与当前线程分离
  • 参数说明:指定要分离的线程ID

线程的属性

默认参数

  • 调度参数:
  • 线程栈地址:
  • 线程栈大小:8M
  • 栈未尾警戒缓冲区大小:PAGESIZE
  • 线程的分离状态:joinable、detached
  • 继承性:PTHREAD_INHERIT_SCHED、PTHREAD_EXPLICIT_SCHED
  • 作用域:PTHREAD_SCOPE_PROCESS、PTHREAD_SCOPE_SYSTEM
  • 调度策略:SCHED_FIFO、SCHED_RR、SCHED_OTHER

相关API函数

  • int pthread_attr_init(pthread_attr_t *attr);
  • int pthread_attr_destroy(pthread_attr_t *attr);
  • int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
  • int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
  • int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
  • int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
  • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
  • int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

线程调度与运行

-核心级线程

  • 由内核调度,有利于并发使用多处理器资源

-用户级线程

  • 由用户层调度,减少上下文切换开销

-线程模型

  • 一对一模型
  • 多对一模型
  • 多对多模型

一对一模型

  • 用户线程通过LWP关联内核线程
  • 线程调度由内核完成
  • SMP、并发使用CPU资源
  • 线程间同步由用户层完成

多对一模型

  • 多个用户线程与一个内核线程关联
  • 线程管理由用户完成、CPU仍以进程为调度单位
  • 单处理器
  • Solaris线程库:Green thread

Linux下的线程

一对一线程模型

  • 一个轻量进程(LWP)对应一个线程
  • 每个LWP都与一个内核线程关联
  • 内核线程,通过LWP绑定,调度用户线程
  • 内核线程被阻塞,LWP也阻塞,用户线程也阻塞
  • 调度由内核完成
    • SCHED_OTHER:分时调度策略
    • SCHED_FIFO:实时调度策略:FIFO
    • SCHED_RR:实时调度策略:时间片轮转
  • 创建线程、同步等API由用户线程库完成
    • Linuxthreads:线程PID、信号处理存在不足
    • NPTL(Native POSIX Thread Library)

线程调度与运行

LWP与普通用户进程比较

  • LWP只有一个最小的执行上下文和调度程序需要的统计信息
  • 用户进程有独立地址空间,LWP与父进程共享地址空间
  • LWP可以像内核线程一样,全局范围内竞争处理器资源
  • LWP调度可以跟用户进程、内核线程一样调度
  • 每一个用户进程可能有一个或多个LWP
  • 通过clone,各进程共享地址和资源
    • CLONE_VM、CLONE_FS
    • CLONE_FILES、CLONE_SIGHAND
  • top -H -p<pid>
  • 查看某个指定PID进程下的线程运行

   pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;

   pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

   pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

   int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

   int pthread_mutex_lock(pthread_mutex_t *mutex);

   int pthread_mutex_trylock(pthread_mutex_t *mutex);

   int pthread_mutex_unlock(pthread_mutex_t *mutex);

   int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值