typedef void*(*start_routine_t)(void*);
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
void pthread_exit(void *retval);
int pthread_setcancelstate(int state, int *oldstate);
int pthread_cancel(pthread_t thread);
int pthread_detach(pthread_t thread);
int pthread_join(pthread_t thread, void **retval);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_destroy(pthread_cond_t *cond);
1.问题的引入
前面讲到 为了并发的执行任务,现代操作系统引入了进程的概念
分析了创建一个子进程的流程
(1)创建一个进程的开销很大 why?
需要copy整个父进程的地址空间
(2)进程间通信 需要用到第三方(如内核)
进程间通信的开销也很大 why?
进程间的地址空间是独立,访问内核时很消耗资源
需要频繁的copy
于是 就有人提出一个问题 能不能在同一个进程内部实现任务的并发执行?
线程/轻量化的进程
2.线程 thread
线程是比进程更小的活动单位,他是进程的一个执行路径(执行分支)
线程也是并发的一种情形
进程内部可以有多个线程 ,它们并发执行,
单进程内部所有的线程共享整个地址空间
main函数是进程的主线程
进程的指令部分分成了多个线程去并发的运行
线程的特点:
(1)创建一个线程比创建一个进程的开销小的多 why
因为创建一个线程 不需要拷贝进程地址空间;
(2)实现线程间的通信十分方便 why
因为一个进程内部所有的线程共享地址空间
(3)线程也是一个动态概念
进程(线程)状态图
ready
running
waitting
(4) 自从有了线程之后
系统的调度的最小单位是线程
系统分配资源的最小单位是进程
线程是进程内一个指令的执行分支,多个线程线程就是多个指令序列并发执行
指令必须在函数内部,线程的指令部分肯定会封装在一个函数的内部
这个函数我们称为 “线程函数”,一个线程创建后,要执行的指令全都在该函数中
这个函数执行完毕 线程的任务也就结束了。
线程函数:
typedef void*(*start_routine_t)(void*);
线程函数:
返回值 可能是char int double* struct 。。。。
这么多类型,难道每个返回值都写一个对应的线程函数
void*!
参数为一个void*指针
返回值为一个void *指针
如
void* myThread(void* arg)
{
}
thread实现的方式有很多中 比较常用的是POSIX 线程
编译时 包含这个参数 -pthread 表示需要调用线程库
比如:gcc main.c -pthread
3.linux下pthread的函数接口
(1) 创建一个线程:
线程有一个线程 id(tid,thread ID),类似于进程的pid
用来表示一个线程的,在pthread中 用类型 pthread_t
来描述一个线程的ID
线程属性
线程栈空间大小; 存放局部变量的
线程的优先级;
......
在pthread中,线程属性用结构体 pthread_attr_t 来描述
开发者提供几个用于改变线程属性的函数接口,不建议
程序员直接修改这个 pthread_attr_t 结构
用默认属性就好了
NAME
pthread_create - create a new threadSYNOPSIS
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
thread: 指向的空间用于保存 成功创建线程后的线程ID
attr: 表示线程的属性 一般为NULL 表示采用默认的属性
当然也可以不用空 去修改线程的属性
start_routine:线程对应的线程函数的指针 线程的任务就是去执行这个函数
arg:线程函数的参数
成功返回0
失败返回-1 同时errno被设置
Compile and link with -pthread.
(2)线程的退出
(1) 线程函数跑完了
(2) 在线程执行的任意时刻调用 pthread_exitm 退出线程 [中间直接退出]
NAME
pthread_exit - terminate calling threadSYNOPSIS
#include <pthread.h>void pthread_exit(void *retval);
void *retval:线程函数的返回值
(3) 线程被别人给干掉了
线程被别人取消了 (其他的线程调用 pthread_cancel(...))
t1:pthread_cancel(t2)
t2是不是一定会被干掉了?不一定
这得看t2线程的属性中表明该线程是否可以被取消
这个可以被取消的属性 pthread提供了一个函数接口(API),去修改它;
NAME
pthread_setcancelstate, pthread_setcanceltype - set cancelability state and
typeSYNOPSIS
#include <pthread.h>int pthread_setcancelstate(int state, int *oldstate);
int state: 要设置的取消属性
用两个宏表示
PTHREAD_CANCEL_ENABLE:该线程可以被取消
PTHREAD_CANCEL_DISABLE:该线程不可被取消了
默认可以被取消
int *oldstate: 上一次的线程的取消状态的属性
比如:
在线程函数里面写:
int *oldstate;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,oldstate);
返回值
成功返回0 失败返回非零的数
那怎么去取消线程呢?
NAME
pthread_cancel - send a cancellation request to a threadSYNOPSIS
#include <pthread.h>int pthread_cancel(pthread_t thread);
pthread_t thread:你要取消的线程ID
成功返回0 失败返回非零的数
(是否取消成功和返回值没有关系,返回值只证明是否运行了此函数)
一个线程退出了,并不代表它所有的资源都被释放
一个进程退出了,它的资源是否被释放,取决于一个属性
deatech;分离属性
ENABLE:分离
该线程结束后,由系统托管,他的资源会全部自动释放
分离之后就没有办法变成非分离
DISABLE:非分离 默认属性
该线程结束后,它会有部分资源不会被自动释放,
需要其他线程调用pthread_join函数才能完全释放
SYNOPSIS
#include <pthread.h>int pthread_detach(pthread_t thread);
pthread_t thread:要分离线程的ID
返回值;
成功返回0 失败返回一个错误码(出错了再去百度含义)
(3)等待一个线程退出
上面讲到非分离状态下,线程结束后,有部分资源不会被自动释放
那么怎么办?
pthread_join
pthread_join:用来等待一个指定的线程退出,然后释放剩余资源;
请注意,如果等待的线程是detach属性,这个线程
由系统托管了,pthread_join直接返回,不会等待。
NAME
pthread_join - join with a terminated threadSYNOPSIS
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
pthread_t thread:要等待那个线程 如果等待的那个线程已经是detach属性
那么这个函数一点用处都没有,因为此时被detach属性
的线程被系统托管了,压根不会等待。
不要对同一个线程调用 pthread_join 和 pthread_detach
void **retval:指向的空间用于保存线程函数的返回值(void *)
(4)线程间的同步和互斥
为了线程间,有序地访问共享资源,也要引入“信号量的机制”
(4.1)信号量(System V sem/POSIX sem)
(4.2) 线程互斥锁
线程互斥锁也是一个信号量,只不过线程互斥锁,
存在进程的地址空间,只能用于该进程的所有线程间的同步和互斥,
线程互斥锁效率要比信号量要高
虚拟机里面终端安装命令
//安装线程互斥锁的说明书
sudo apt-get install manpages-posix-dev
(1)初始化一个线程互斥锁
NAME
pthread_mutex_init — destroy and initialize a mutexSYNOPSIS
#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
mutex: 待初始化的线程互斥锁的指针
attr: 线程互斥锁属性,一般为NULL,采用默认的属性
默认初始化后的值为 1 unlock
返回值 成功返回0 失败返回非零的值
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
(2)P/V操作
pthread_mutex_lock "阻塞等待" 一直等待,直到成功上锁
pthread_mutex_trylock "非阻塞等待" 尝试一次获取 获取不了就走
pthread_mutex_timedlock "限时等待"
SYNOPSIS
#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
这个两个函数的参数是一样的 mutex 都是表示对那把锁进行操作
pthread_mutex_lock 表示“死等” 如果锁不可用,则一直等待
该锁可以用未知或者被信号打断
pthread_mutex_trylock 表示能获取则获取 不能获取则立马返回
SYNOPSIS
#include <pthread.h>
#include <time.h>int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
mutex 表示对那把锁进行操作
abstime:绝对时间 如果想要等待1秒中
那么就要 获取当前的时间 + 要等待的时间
[ 绝对时间 = 当前时间 + 等待时间 ]
这个结构体具体怎么用 看POSIX信号量对应P操作
三个P操作的函数的返回值
成功返回0 表示获取到了这把互斥锁 -》可以进入到临界区执行了
失败返回非零的值 表示没有获取到这把互斥锁 -》不可以进入临界区执行
V操作 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex 要对那一把锁进行解锁
返回值
成功返回0
失败返回非零的值
(3)销毁操作
SYNOPSIS
#include <pthread.h>int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:哪一个线程互斥锁不要了 需要销毁呢?
(5)生产者-消费者模型
1.共享资源互斥访问的问题
资源生成和消耗速度不匹配
2.消费者怎么知道缓冲区中的有没有数据?
a.轮询 不断的询问 看有没有数据
轮询的缺陷
1.浪费CPU
2.有时间差 不及时
3.占用总线
b.让出CPU(sleep) 当CPU有数据的时候 再叫我(唤醒)
线程的条件变量 同步
线程的条件变量到底是一个什么东西呢?
在多线程程序设计中,我们可以用“条件变量”来表示
一个特定的事件或条件
由于这个条件变量是由程序员来定义或解释的
到底表示什么条件/事件完全由程序员来决定
条件变量一共由几个操作?
四个
初始化一个条件变量
等待一个条件变量(等待的过程中 让出CPU的使用权 直到条件满足被唤醒)
唤醒一个条件变量(条件满足吗,唤醒正在等待的该条件变量的线程)
销毁一个条件变量
初始化
t2线程
if(条件不满足/事件没产生)
{
sleep(休眠 等待被唤醒);
}
t1
if(条件满足了 事件产生了)
{
wakeUp(叫醒等待的线程)
} ------------------------
条件变量的API
(1)初始化一个条件变量
SYNOPSIS
#include <pthread.h>int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
cond:要初始化的条件变量
pthread_cond_t cond;
&cond
attr:条件变量的属性 一般为NULL,表示采用一个默认属性
返回值
成功返回0
失败返回非零的值
(2)等待一个条件变量
SYNOPSIS
#include <pthread.h>int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
cond: 等待那个条件变量
mutex:线程互斥锁 为了保护cond所代表事件或共享资源的
在调用这个函数的时候 meteu是初始locked一个状态
这难道不会有问题吗?
不会
pthread_cond_wait()
{
//mutex locker
......
Unlocked
让出CPU
......
当条件(条件变量和互斥解锁)满足 其他生产者唤醒你的时候
Lock Mutex;
}
把条件变量和互斥锁称为一对mc
表明这两个东西是成对出现的
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
abstime:绝对时间 表示要超时了 不论条件变量
是否被唤醒 都继续往下执行 不睡了
(3)唤醒一个条件变量:唤醒正在等待条件变量cond的线程
SYNOPSIS
#include <pthread.h>
//唤醒所有等待cond条件变量的线程
int pthread_cond_broadcast(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond);
//只唤醒一个等待cond的线程
(4)销毁一个条件变量
SYNOPSIS
#include <pthread.h>int pthread_cond_destroy(pthread_cond_t *cond);
你要销毁那个条件变量
返回值
成功返回0
失败返回非零的值