-
进程和线程区别
- 进程有独立的地址空间
- Linux为每个进程创建task_struct
- 每个进程都参与内核调度,互不影响
- 进程在切换时系统开销大
- 很多操作系统引入了轻量级进程LWP
- 同一进程中线程共享相同地址空间
- Linux不区分进程、线程
-
线程特点
- 通常线程指的是共享相同地址空间的多个任务
- 使用多线程的好处
- 大大提高了任务切换的效率
- 避免了额外的TLB&cache的刷新
-
线程共享资源(公有资源)
- 一个进程中的多个线程共享以下资源:
- 可执行的命令
- 静态数据
- 进程中打开的文件描述符
- 当前工作目录
- 用户ID
- 用户组ID
- 一个进程中的多个线程共享以下资源:
-
线程私有资源
- 每个线程私有的资源包括:
- 线程ID(TID)
- PC(程序计数器)和相关寄存器
- 堆栈
- 错误号(errno)
- 优先级
- 执行状态和属性
- 每个线程私有的资源包括:
-
linux线程库
- pthread线程库提供了如下基本操作
- 创建线程
- 回收线程
- 结束线程
- 同步和互斥机制
- 信号量
- 互斥锁
- pthread线程库提供了如下基本操作
-
线程创建pthread_create
- #include <pthread.h>
- int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void*) *arg);
- 成功返回0,失败返回错误码
- thread线程对象
- attr线程属性,NULL代表默认属性
- routine线程执行的函数
- arg传递给toutine的参数,参数是void*,注意传递参数格式
- pthread_t pthread_self(void) 查看自己的TID
-
线程运行特点:
- 主进程的退出,他创建的线程也会退出。
- 线程创建需要时间,如果主进程马上退出,那线程不能得到执行
-
线程结束-pthread_exit
- #include <pthread.h>
- void pthread_exit(void *retval);
- 结束当前线程
- retval可被其他线程通过pthread_join获取
- 线程私有资源被释放
-
获取线程ID
- 通过pthread_create函数的第一个参数,通过在线程里面调用pthread_self()
- tid获取
-
线程的参数传递
- 通过地址传递参数,注意类型转换
-
void*类型指针不能直接用*取值,因为编译不知道数据类型。解决方法:转换为指定的指针类型后在用*取值
-
- 通过值传递,这时候编译器会告警,需要程序员自己保证数据长度正确
- 通过地址传递参数,注意类型转换
-
线程的回收-pthread_join
- #include<pthread.h>
- int pthread_join(pthread_t thread, void **retval);
- 对于一个默认属性的线程A来说,线程占用的资源并不会因为执行结束而得到释放
- 成功返回0,失败返回错误码
- thread要回收的线程对象
- 调用线程阻塞到thread结束
- *retval接收线程thread的返回值
- 注意:pthread_join是阻塞函数,如果回收的线程没有结束,他就会一直等待
-
线程分离两种方式实现回收
- int pthread_detach(pthread_t thread);
- 成功:0, 失败:错误号
- 指定该状态,线程主动与主空线程断开关系。线程结束后(不会产生僵尸线程)
- pthread_attr_t attr; /*通过线程属性来设置游离态(分离态)*/
- 设置线程属性为分离
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- 设置线程属性为分离
- int pthread_detach(pthread_t thread);
-
线程回收内存演示
- 通过进程占用内存可以看出来线程回收和不回收的区别
-
线程的取消和清理
- int pthread_cancel(pthread_t thread);杀死一个线程
- 注意:线程的取消要有取消点才可以,不是说取消就取消,取消点是阻塞的系统调用
- 运行段错误:可以使用gdb调试
- 如果没有取消点,手动设置一个
- void pthread_testcancel(void);
- int pthread_setcancelstate(int state, int * oldstate);
- state传参数
- PTHREAD_CANCEL_ENABLE
- PTHREAD_CANCEL_DISABLE
- state传参数
- int pthread_setcanceltype(int type, int *oldtype);//设置取消类型
- PTHREAD_CANCEL_DEFERRED//等到取消点才取消,延时取消
- PTHREAD_CANCEL_ASYNCHROUNOUS//目标线程会立即取消
- int pthread_cancel(pthread_t thread);杀死一个线程
-
线程的清理
- void pthread_cleanup_push(void (*routine)(void*),void *arg)
- 第一个参数是函数指针,返回值void
- 第二个参数是函数参数
- routine函数被执行的条件:
- 被pthread_cancel取消掉
- 执行pthread_exit
- 非0参数执行pthread_cleanup_pop()
- void pthread_cleanup_pop(int execute)
- 两个函数必须成对使用,只写一个编译都不通过,即使pthread_cleanup_pop不会被执行到也必须写上。
- pthread_cleanup_pop()被执行且参数为0,pthread cleanup_push回调函数routine不会被执行
- pthread_cleanup_push 和pthread_clean_pop可以写成多对,routine执行顺序正好相反
- 线程内的return可以结束线程但是不能触发pthread_cleanup_push里面的回调函数
- void pthread_cleanup_push(void (*routine)(void*),void *arg)
-
线程的同步和互斥
- 临界资源
- 一次只允许一个任务(进程、线程)访问的共享资源
- 比如写文件,只能由一个线程写,同时写会写乱
- 比如外设打印机打印的时候只能由一个程序使用
- 外设基本上都是不能共享的资源。
- 生活中必入卫生间,同一时间只能由一个人使用。
- 必要性:临界资源不可以共享
- 临界区
- 访问临界资源的代码
- 互斥机制
- mutex互斥锁
- 任务访问临界资源前申请锁,访问完后释放锁
- man手册找不到pthread_mutex_xxxxxx出现No manual entry for pthread_mutex_xxx解决方式:
- apt-get install manpages-posix-dev
-
互斥锁的创建和销毁
- 两种方法创建互斥锁,静态方式和动态方式
- 动态方式:
- 初始化pthread_mutex_init
- #include<pthread.h>
- int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t* attr)
- 成功时返回0,错误返回错误码
- mutex执行要初始化的互斥锁对象
- attr用于指定互斥锁属性,NULL表示缺省属性
- 初始化pthread_mutex_init
- 静态方式:
- pthread_mutext_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 锁的销毁:
- int pthread_mutex_destroy(pthread_mutext_t *mutex)
- 在Linux中,互斥锁不占用任何资源,因此LinuxThreads中的pthread_mutext_destroy()除了检查锁状态以外(锁定状态返回EBUSY)没有其他动作。
- 互斥锁的使用:
- int pthread_mutex_lock(pthread_mutex_t *mutex)申请锁
- int pthread_mutex_unlock(pthread_mutex_t *mutex)释放锁
- int pthread_mutex_trylock(pthread_mutex_t *mutex)
- 成功返回0,失败返回错误码
- mutex指向要初始化的互斥锁对象
- pthread_mutex_lock如果无法获得锁,任务阻塞
- pthread_mutex_trylock如果无法获得锁,返回EBUSY而不是挂起等待
- 如果有多个临界资源的话,需要定义多个锁
- vim设置全文格式化:gg=G
- 读写锁
- 必要性:提高线程执行效率
- 特性:
- 写者:使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁,否则写者将等待,直到没有读者和写者
- 读者:读者使用读锁,如果当前没有写者,读者立即获得读锁,否则读者等待,直到没有写者
- 注意:同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。读写锁处于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁清秋会被阻塞,以避免写者长时间的不写锁。
- 初始化一个读写锁pthread_rwlock_init
- 读锁定读写锁pthread_rwlock_rdlock
- 非阻塞读锁定pthread_rwlock_tryrdlock
- 写锁定读写锁pthread_rwlock_wrlock
- 非阻塞写锁定pthread_rwlock_trywrlock
- 解锁读写锁pthread_rwlock_unlock
- 释放读写锁pthread_rwlock_destroy
- 死锁
- 概念:
- 死锁只有多把锁的时候出现,一把锁不会,互相获取对方的资源
- 避免方法:
- 锁越少越好,最好使用一把锁
- 如果一把锁不够,一定要调整好锁的顺序
- 条件变量
- 应用场景:
- 生产者消费者问题
- 必要性:为了实现等待某个资源,让线程休眠,提高运行效率
- pthread_cond_wait(&m_cond, &m_mutex);// 等待资源
- int pthread_cond_timedwait(pthread_cond_t * restrict cond,pthread_mutex_t *restrict mutex, const struct timespec * restrict abstime); // 等待超时退出
- int pthread_cond_signal(pthread_cond_t *cond);// 信号
- int pthread_cond_broadcast(pthread_cond_t *cond);//广播
- 使用步骤:
- pthread_cond_t cond = PTHREAD_COND_INITIALIZER// 初始化条件变量
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER//初始化互斥量
- 生产资源线程:
- pthread_mutex_lock(&mutex);//加锁
- 开始生产资源:
- pthread_cond_sigal(&cond)://通知一个消费线程,单播
- 或者
- pthread_cond_broadcast(&cond);//广播通知多个消费线程
- pthread_mutex_unlock(&mutex);
- 消费者线程:
- pthread_mutex_lock(&mutex);
- while(如果没有资源){//防止惊群效应
- pthread_cond_wait(&cond, &mutex);//等待资源
- }
- 有资源了,消费资源
- pthread_mutex_unlock(&mutex);
- 注意:
- pthread_cond_wait(&cond,&mutex)在没有资源等待是先unlock休眠,等资源到了,再lock,所以pthread_cond_wait 和pthread_mutex_lock必须配对使用
- 如果pthread_cond_signal或者pthread_cond_broadcast早于pthread_cond_wait,可能会导致信号丢失
- pthread_cond_wait(&cond,&mutex)在没有资源等待是先unlock休眠,等资源到了,再lock,所以pthread_cond_wait 和pthread_mutex_lock必须配对使用
-
pthread_cond_broadcast信号会被多个线程收到,这叫线程的惊群效应。所以需要加上判断条件while循环。
-
#include<pthread.h> #include<stdio.h> #include<unistd.h> #include<stdlib.h> pthread_cond_t hasTaxi = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; struct taxi{ struct taxi *next; int num; }; struct taxi *Head = NULL; void *taxiarv(void *arg){// 生产者线程 printf("taxi arrived thread\n"); pthread_detach(pthread_self()); struct taxi *tx; int i = 0; while(1){ tx = malloc(sizeof(struct taxi)); tx->num = i++; printf("taxi %d comming\n", tx->num); pthread_mutex_lock(&lock); tx->next = Head; Head = tx; //pthread_cond_signal(&hasTaxi);// 生产者释放信号告诉消费者有资源了 pthread_cond_broadcast(&hasTaxi); pthread_mutex_unlock(&lock); sleep(1); // 每秒生产一辆车 } pthread_exit(0); } void *taketax(void *arg){// 消费者线程 printf("take taxi thread\n"); pthread_detach(pthread_self()); struct taxi *tx; while(1){ pthread_mutex_lock(&lock); while(Head==NULL){// 没有资源的时候等待,防止下面崩溃,必须加这while Head==NULL pthread_cond_wait(&hasTaxi, &lock);// 1.条件变量 2.锁 收到信号解锁 } tx = Head; // 消费资源 Head=tx->next; printf("%d, Take taxi %d\n", (int)arg, tx->num); free(tx); pthread_mutex_unlock(&lock); } pthread_exit(0); } int main(){ pthread_t tid1, tid2, tid3, tid4; pthread_create(&tid1, NULL, taxiarv, NULL); sleep(5); pthread_create(&tid2, NULL, taketax, (void*)1); pthread_create(&tid3, NULL, taketax, (void*)2); pthread_create(&tid4, NULL, taketax, (void*)3); while(1){ sleep(1); } }
- 应用场景:
- 临界资源
-
线程池
- 概念:通俗的讲就是一个线程的池子,可以循环的完成任务的一组线程集合
- 必要性:
- 我们平时创建一个线程,完成某一个任务,等待现成的退出。但当需要创建大量的线程时,假设T1为创建线程时间,T2为线程任务执行事件,T3为线程销毁时间,当T1+T3>T2,这时候就不划算了,使用线程池可以降低频繁创建和销毁线程所带来的开销,任务处理时间比较短的时候这个好处非常显著。
- 线程池的基本结构:
- 任务队列,需要处理的任务,由工作线程来处理这些任务
- 线程池工作线程,他是任务队列任务的消费者,等待新任务的信号
-
线程池的实现:
-
创建线程池的基本结构:
-
任务队列链表
-
typedef struct Task
-
-
线程池结构体
-
typedef struct ThreadPool
-
-
-
线程池的初始化
-
pool_init(){
-
创建一个线程池结构
-
实现任务队列互斥锁和条件变量的初始化
-
创建n个工作线程
-
-
}
-
-
线程池添加任务
-
pool_add_task{
-
判断是否有空闲的工作线程
-
给任务队列添加一个节点
-
给工作线程发送信号newtask
-
-
}
-
-
实现工作线程
-
workThread{
- while(1){
- 等待newtask任务信号
- 从任务队列中删除节点
- 执行任务
- }
- while(1){
-
}
-
-
线程池的销毁
-
pool_destory{
-
删除任务队列链表所有节点,释放空间
-
删除所有的互斥锁条件变量
-
删除线程池,释放空间
-
-
}
-
-
-
#include<pthread.h> #include<stdio.h> #include<unistd.h> #include<stdlib.h> #define POOL_NUM 10 typedef struct Task{ void *(*func)(void *arg); void *arg; struct Task *next; }; typedef struct ThreadPool{ pthread_mutex_t taskLock;//互斥量 pthread_cond_t newTask; // 条件变量 pthread_t tid[POOL_NUM]; // 线程数组 struct Task *queue_head; int busywork; }ThreadPool; ThreadPool *pool; void *workThread(void *arg){ while(1){ pthread_mutex_lock(&pool->taskLock); pthread_cond_wait(&pool->newTask, &pool->taskLock); struct Task *ptask = pool->queue_head; pool->queue_head = pool->queue_head->next; pthread_mutex_unlock(&pool->taskLock); ptask->func(ptask->arg); pool->busywork--; } } void *realwork(void *arg){ printf("finish work %d\n", (int) arg); } void pool_add_task(int arg){ struct Task *newTask; pthread_mutex_lock(&pool->taskLock); while(pool->busywork>=POOL_NUM){ pthread_mutex_unlock(&pool->taskLock); usleep(10000);//休眠的时候把锁释放,再次判断的时候加锁 pthread_mutex_lock(&pool->taskLock); } pthread_mutex_unlock(&pool->taskLock); newTask = malloc(sizeof(struct Task)); newTask->func = realwork;//函数指针的使用 newTask->arg = (void*)arg; pthread_mutex_lock(&pool->taskLock); struct Task *member = pool->queue_head; if(member==NULL){ pool->queue_head = newTask; }else{ while(member->next!=NULL){ member=member->next; } member->next = newTask;//遍历到尾部加入新任务 } pool->busywork++; pthread_cond_signal(&pool->newTask); pthread_mutex_unlock(&pool->taskLock); } void pool_init(){ pool = malloc(sizeof(struct ThreadPool)); pthread_mutex_init(&pool->taskLock, NULL); pthread_cond_init(&pool->newTask, NULL); pool->queue_head = NULL; pool->busywork=0; for(int i=0;i<POOL_NUM;i++){ pthread_create(&pool->tid[i], NULL, workThread, i); } } void pool_destory() { struct Task *head; while(pool->queue_head!=NULL){ head = pool->queue_head; pool->queue_head =pool->queue_head->next; free(pool->queue_head); } pthread_mutex_destroy(&pool->taskLock); pthread_cond_destroy(&pool->newTask); free(pool); } int main(){ pool_init(); sleep(1); for(int i=1;i<=20;i++){ pool_add_task(i); } sleep(5); pool_destory(); return 0; }
-
线程的gdb调试
- 显示线程info thread
- 切换线程
- thread xxx
- bt
- GDB为特定线程设置断点
- break location thread id
- GDB设置线程锁
- set scheduler-locing on/off
12.线程创建回收取消清理
最新推荐文章于 2024-07-11 16:57:52 发布