华清远见25072班I/O学习day6
重点内容:
一、多线程基本概念
1> 线程:也称为轻量版的进程(LWP),是更小的任务执行单元,是进程的一个执行路径
2> 线程是任务器调度的最小单位,进程是资源分配的基本单位
3> 一个进程中可以包含多个线程,多个线程共享进程的资源。
4> 线程几乎不占用资源,只是占用了很少的用于线程状态的资源(大概有8k左右)
5> 由于多个线程共同使用进程的资源,导致,线程在操作上容易出现不安全的状态
6> 线程操作开销较小、任务切换效率较高
7> 一个进程中,至少要包含一个线程(主线程)
8> 在有任务执行漫长的IO等待过程中,可以同时执行其他任务
9> linux中不直接支持线程相关的支持库,需要引入第三方库,线程支持库
sudo apt-get install manpages-posix manpages-posix-dev
如果程序中使用的线程支持库中的函数,编译程序时,需要加上 -lpthread 选项
二、多线程编程
2.1 线程的创建:pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建一个分支线程,并启动该线程
参数1:用于返回线程号的参数,传递一个pthread_t类型的变量地址,创建结束后,会得到该线程的 tid
参数2:创建的线程属性,一般填NULL,使用系统默认线程属性
参数3:线程体函数,是一个函数指针,参数为void*返回值也为void*
参数4:用于主线程,向分支线程传递数据使用
返回值:成功返回0,失败返回一个错误码(注意,不是内核提供的错误)
2.2 线程号获取:pthread_self
pthread_t pthread_self(void);
功能:获取当前线程的线程号
参数:无
返回值:返回当前线程的线程号,该函数只会成功不会失败
2.3 线程退出:pthread_exit
void pthread_exit(void *retval);
功能:退出该函数所在的线程
参数:线程的退出状态,一般填NULL
返回值:无
2.4 线程资源回收:pthread_join / pthread_detach
int pthread_join(pthread_t thread, void **retval);
功能:阻塞回收指定的线程结束
参数1:要回收线程的线程号
参数2:回收线程退出时的状态,一般填NULL
返回值:成功返回0,失败发那会一个错误码
int pthread_detach(pthread_t thread);
功能:将某个线程设置成分离态,当某个线程设置成分离态后,其退出时,资源由系统回收 参数:要被设置成分离态的线程tid号
返回值:成功返回0,失败返回错误码
三、线程同步互斥
1> 竞态:同一个进程的多个线程在访问临界资源(公共资源)时,会出现抢占的现象,导致线程中的数据错误的现象称为竞态
2> 临界资源:多个线程共同访问的数据称为临界资源,可以是全局变量、堆区数据、外部文件
3> 临界区:访问临界资源的代码段称为临界区
4> 粒度:临界区的大小
5> 同步:表示多个任务有先后顺序的执行
6> 互斥:表示在访问临界区时,同一时刻只能有一个任务,当有任务在访问临界区时,其他任务只能等待
3.1 互斥机制
1> 互斥机制使用互斥锁来完成,互斥锁时一个特殊的全局变量(临界资源),该互斥锁被某一个线程获取后,其他线程想要获取该锁资源就只能等待,直到拥有该锁资源的线程释放了互斥锁。
2> 同一时刻,只能有一个线程拥有锁资源
3> 有关互斥锁的API
1、使用pthread_mutex_t类型的变量定义一个全局变量,就有了一个互斥锁 pthread_mutex_t mutex ;
2、初始化互斥锁 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化一个互斥锁
参数1:要被初始化的互斥锁地址
参数2:互斥锁属性,一般填NULL,由系统自动设置
返回值:没有失败,成功返回0
3、获取锁资源 int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:获取指定的锁资源,如果该锁资源已经被其他线程获取,那么该函数会阻塞,直到要获取的锁资源被释放
参数:要获取的锁资源地址
返回值:成功返回0,失败返回非0的错误码
4、释放锁资源 int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:释放指定的互斥锁资源
参数:互斥锁地址
返回值:成功返回0,失败返回非0的错误码
5、销毁互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁指定的互斥锁
参数:互斥锁地址
返回值:成功返回0,失败返回非0的错误码
3.2 线程同步机制
1> 线程同步机制表示要将多个线程有先后顺序的执行
2> 经典的线程同步问题:生产者消费者问题,生产者任务用于生产,通知消费者执行
3> 线程同步机制有两个:无名信号量(一个生产者对应一个消费者)、条件变量(一个生产者对应多个消费者)
3.2.1 无名信号量
1> 无名信号量本质上维护的是一个value值,当该值大于0时,表示有资源可以被申请,申请资源的函数直接获取
当value值等于0时,表示没有资源可以被获取,申请资源的函数会被阻塞
2> 无名信号量也是一个特殊的全局变量(临界资源)
3> 无名信号量相关API
1、创建一个无名信号量:只需定义一个sem_t类型的全局变量即可 sem_t sem;
2、初始化无名信号量 int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个无名信号量
参数1:无名信号量地址
参数2:标识是进程间共享还是线程间共享 0:标识线程的同步 非0表示多个亲缘进程间同步 参数3:资源的初始值
返回值:成功返回0,失败返回-1并置位错误码
3、申请无名信号量资源(P操作) int sem_wait(sem_t *sem);
功能:申请无名信号量的资源操作,如果无名信号量中的资源大于0,则直接继续执行后面操作,如果无名信号量中的资源等于0,则该函数会阻塞 直到其他线程将该无名信号量的资源增加到大于0
参数:无名信号量的地址
返回值:成功返回0,失败返回-1并置位错误码
4、释放无名信号量(V操作)int sem_post(sem_t *sem);
功能:释放无名信号量的资源,使得无名信号量资源增加操作
参数:无名信号量地址
返回值:成功返回0,失败返回-1并置位错误码
5、销毁无名信号量 int sem_destroy(sem_t *sem);
功能:销毁一个无名信号量
参数:无名信号量地址
返回值:成功返回0,失败返回-1并置位错误码
3.2.2 条件变量
1> 条件变量本质上维护了一个队列,对于消费者而言,想要执行的话,先进入等待队列中进行休眠,直到生产者唤醒后继续执行
2> 对于生产者唤醒消费者的行为:先进入等待队列的消费者先执行
3> 多个消费者在进入等待队列中,会产生竞态,此时需要一个互斥锁
4> 条件变量相关API
1、创建一个条件变量:只需要定义一个pthread_cond_t类型的全局变量,就创建了一个条件变量 pthread_cond_t cond ;
2、初始化条件变量 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
功能:初始化条件变量
参数1:条件变量的地址
参数2:条件变量的属性,一般为NULL,表示使用系统默认属性
返回值:成功给返回0,失败返回-1并置位错误码
3、消费者线程进入等待队列,并休眠 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:将当前线程放入等待队列中并进入休眠状态
参数1:条件变量地址
参数2:是一个互斥锁,表示多个线程进入阻塞队列时,是互斥进入的
返回值:成功给返回0,失败返回-1并置位错误码
4、唤醒等待队列中的线程函数 int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒在等待队列中的第一个线程开始执行
参数:条件变量地址 返回值:成功给返回0,失败返回-1并置位错误码
唤醒等待队列中的线程函数 int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒在等待队列中的所有线程
参数:条件变量地址
返回值:成功给返回0,失败返回-1并置位错误码
5、销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量
参数:条件变量地址
返回值:成功给返回0,失败返回-1并置位错误码
作业:
1.创建三个线程,线程1打印A,线程2打印B,线程3打印C,使用线程同步机制,完成输出结果为:ABCABCABCABC......
程序源码:
#include <25072head.h>
//1.定义三个无名信号量
sem_t sem1,sem2,sem3;
//定义线程体1
void *task1(void *arg)
{
char buf='A';
while(1)
{
//3.申请1号无名信号量资源
sem_wait(&sem1);
write(1,&buf,sizeof(buf));
//4.释放2号无名信号量资源
sem_post(&sem2);
}
}
///定义线程体2
void *task2(void *arg)
{
char buf='B';
while(1)
{
sem_wait(&sem2);
write(1,&buf,sizeof(buf));
sem_post(&sem3);
}
}
//定义线程体3
void *task3(void *arg)
{
char buf='C';
while(1)
{
sem_wait(&sem3);
write(1,&buf,sizeof(buf));
sem_post(&sem1);
}
}int main(int argc, const char *argv[])
{
//2.初始化无名信号量
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);//定义3个线程变量
pthread_t tid1,tid2,tid3;//创建3个线程
if(pthread_create(&tid1, NULL, task1, NULL) != 0)
{
printf("tid1 create error\n");
return -1;
}if(pthread_create(&tid2, NULL, task2, NULL) != 0)
{
printf("tid2 create error\n");
return -1;
}
if(pthread_create(&tid3, NULL, task3, NULL) != 0)
{
printf("tid3 create error\n");
return -1;
}//主线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);//5.销毁无名信号量
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);return 0;
}
3.思维导图: