目录
一、线程概念
线程概念:
有一个零件加工工厂,工厂中有一个或多个工人
工人是干活的,工厂是集体设备资源的载体
进程就是工厂,线程就是工人。
进程是系统进行资源分配的基本单元;线程是系统进行调度的基本单元。
线程就是进程中的一条执行流程。Linux下线程执行流程是通过pcb实现的,而一个进程中可以有多个线程(pcb),并且这些pcb共享了进程中的大部分资源,相较于传统pcb较为轻量化,因此linux下的线程也被称作为轻量级进程。
通俗理解:在最早学习进程的时候,认为进程就是pcb,是一个程序运行的动态描述,通过这个描述符操作系统实现程序运行调度以及管理。
但是在学习到线程的时候,才知linux下的pcb实际上是一个线程,是进程中的一条执行流程,一个进程中可以有多个pcb,这些pcb共享进程中的大部分资源,因此也被成为轻量级进程。
线程间的独有与共享:
独有:标识符,栈,寄存器,信号屏蔽字,errno…
共享:虚拟地址空间,IO信息,信号处理方式,工作路径…
多进程与多线程进行多任务处理的优缺点:
线程优点:
1.线程间通信更加灵活(包括进程间通信方式在内还可以使用全变量或者函数传参来实现)
2.线程的创建与销毁成本更低。
3.线程间的切换调度成本更低。
进程优点:
稳定,健壮性高。—主程序安全性要求高的场景–shell、服务器
多任务处理中,并不是进程或者线程越多越好。执行流太多反而会增加切换调度的成本。
IO密集型程序:程序中大量进行IO操作
cpu密集型程序:程序汇总大量进行数据运算操作
二、线程控制
线程控制:创建,退出,等待,分离
linux并没有向上提供用于创建线程的接口,因此大牛们对系统调用接口进行封装实现了上层用户态的线程控制接口
创建
int pthread_create(pthread_t *tid, pthread_attr_t *attr, void*(*thread_routine)(void*), void *arg)
参数说明:
pthread_t *tid:用于获取线程id,tid是线程的操作句柄,实际上是线程在虚拟地址空间中自己所相对独有的一块空间的首地址
pthread_attr_t *attr:用于线程属性设置
void*(*)(void*):线程入口函数
void *arg:传递给线程的数据
返回值:成功返回0;失败返回非0值
pthread_self()
返回线程id
退出
退出:如何退出线程
线程入口函数中return(线程入口函数运行完毕,则线程会退出)
主线程main函数中return退出的是进程
void pthread_exit(void *retval);
void pthread_exit(void *retval); 在任意位置调用都可以退出线程
主线程退出,并不会导致进程退出;所有线程退出,才会退出进程
进程退出,会退出所有的线程
int pthread_cancel(pthread_t thread);
int pthread_cancel(pthread_t tid);
取消指定的线程(被动退出)
等待
等待:等待一个指定的线程退出,获取退出线程的返回值,回收线程资源。
线程推出后,默认也不会自动释放资源,需要被等待。
处于joinable状态的线程退出后不会自动释放资源
//阻塞接口
int pthread_join(pthread_t tid, void **retval)
tid:指定要等待的线程id;
retval:用于获取退出线程返回值
返回值:成功返回0;失败返回非0-错误编号;
join实例
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void *thr_entry(void *arg)
{
char *ptr = "nice day\n";
sleep(3);
return (void*)ptr;
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_entry, NULL);
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
void *retval = NULL;
pthread_join(tid, &retval);
printf("retval:%s\n", retval);
while(1)
{
printf("i am main thread\n");
sleep(1);
}
return 0;
}
分离
线程有个属性-分离属性,这个属性默认是joinable状态,处于joinable的线程推出之后不会自动释放资源,需要被其他线程等待。
分离:将线程的分离属性,设置为detach状态
处于detach状态的线程退出后,会自动释放资源,不需要被等待。
应用场景:不关心线程的退出返回值,也不想等待一个线程退出
int pthread_detach(pthread_t tid);
detach实例
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void *thr_entry(void *arg)
{
char *ptr = "nice day\n";
sleep(3);
return (void*)ptr;
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_entry, NULL);
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
pthread_detach(tid);
//void *retval = NULL;
//pthread_join(tid, &retval);
//printf("retval:%s\n", retval);
while(1)
{
printf("i am main thread\n");
sleep(1);
}
return 0;
}
## 查看
```powershell
ps -ef -L | grep create
ps -ef | grep create:查看create程序的进程信息
-L:查看轻量级进程信息
在每个线程的pcb中都有一个pid和tgid
pid是轻量级进程id-LWP;tgid是线程组id-默认等于主线程的pid
tid:这是一个地址–线程独有空间的地址
每个线程被创建,都会在进程的虚拟地址空间中开辟出一块相对独立的空间
实例
//create.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void *thr_entry(void *arg)
{
printf("%s\n", (char*)arg);
while(1)
{
printf("i am normal thread--%p-%d\n", pthread_self(), getpid());
sleep(5);
pthread_exit(NULL);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret;
char *ptr = "nice day\n";
ret = pthread_create(&tid, NULL, thr_entry, (void*)ptr);
if(ret != 0)
{
printf("pthread_create failed!\n");
return -1;
}
//sleep(3);
//pthread_cancel(tid);
while(1)
{
//打印线程id和进程id
printf("i am main thread--%p-%d\n", pthread_self(), getpid());
sleep(1);
}
return 0;
}
三、线程安全
概念
概念:描述的是线程中对临界资源的访问操作是安全的
实现:同步与互斥
同步:通过条件判断使对临界资源访问或获取更加合理
互斥:通过对临界资源同一时间的唯一访问保证访问操作安全
互斥
互斥的实现:互斥锁
互斥锁:本质就是一个只有0/1的计数器,用于标记临界资源的访问状态;0-不可访问;1-可访问。
实现互斥原理,在访问临界资源之前加锁:通过计数器判断是否可访问,不可访问则阻塞;访问资源完毕之后解锁:通过计数器将资源标记为可访问,唤醒阻塞。
互斥锁自身计数的操作是一个原子操作。
接口介绍:
1.定义互斥锁变量
pthread_mutex_t mutex;
2.初始化互斥锁变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
3.在访问临界资源之前加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);加锁失败则立即返回错误编号-EBUSY
4.在访问临界资源之后解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5.销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
int tickets = 100;
void *scalpers(void *arg)
{
pthread_mutex_t *mutex = (pthread_mutex_t*)arg;
while(1)
{
pthread_mutex_lock(mutex);
if(tickets > 0)
{
usleep(1);
printf("I got a ticket: %d\n", tickets);
tickets--;
}
else
{
pthread_exit(NULL);
pthread_mutex_unlock(mutex);
}
pthread_mutex_unlock(mutex);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_mutex_t mutex;
pthread_t tid[4];
int ret;
pthread_mutex_init(&mutex, NULL);
for(int i = 0; i < 4; i++)
{
ret = pthread_create(&tid[i], NULL, scalpers, &mutex);
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
}
for(int i = 0; i < 4; i++)
{
pthread_join(tid[i], NULL);
}
pthread_mutex_destroy(&mutex);
return 0;
}
死锁:程序运行流程因为某种原因卡死无法继续推进
死锁产生的原因:死锁产生的四个必要条件
1.互斥条件:一个资源同一事件只有一个进程/线程能够访问
2.不可剥夺条件:我加的锁只有我能解,别人不能解
3.请求与保持条件:加了A锁后请求B锁,B请求不到,A不释放
4.环路等待条件:线程1加了A锁,请求B锁;线程2加了B请求A
预防死锁:破坏死锁产生的必要条件(3.4)
1.一定保证加/解锁顺序一致
2.请求不到第二个锁则释放已有的
避免死锁:银行家算法…
已有资源—线程已有资源—线程请求新的资源
同步
同步的实现:通过条件判断实现对资源获取的合理性–条件变量
条件变量:pcb等待队列+能够使线程阻塞以及唤醒线程阻塞的接口
条件变量使用中,对条件判断由程序员自己完成,而条件判断的依据使一个临界资源,访问时需要被保护,因此条件变量需要搭配互斥锁一起使用。
接口介绍
1.定义条件变量
pthread_cond_t cond;
2.初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
3.使线程阻塞
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *abstime)
4.唤醒阻塞的线程:
int pthread_cond_signal(pthread_cond_t *cond);-至少唤醒一个
int pthread_cond_broadcast(pthread_cond_t *cond);-唤醒所有
5.销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
注意:条件变量需要搭配互斥锁一起使用;
因此资源是其他线程促使条件满足,其中的条件是否满足也是临界资源的判断。因此需要加锁保护。
pthread_cond_wait包含了解锁,休眠,被唤醒后加锁三步
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond_customer;
pthread_cond_t cond_cooker;
int bowl = 1;
void *cooker(void *arg)
{
while(1)
{
//lock
pthread_mutex_lock(&mutex);
while(bowl == 1)
{
//wait
pthread_cond_wait(&cond_cooker, &mutex);
}
//cook
printf("cook finish\n");
bowl++;
//wake customer
pthread_cond_signal(&cond_customer);
//unlock
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *customer(void *arg)
{
//lock
pthread_mutex_lock(&mutex);
while(1)
{
while(bowl == 0)
{
//wait
pthread_cond_wait(&cond_customer, &mutex);
}
//eat
printf("nice\n");
bowl--;
//wake cooker
pthread_cond_signal(&cond_cooker);
//unlock
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t ctid, dtid;
int ret;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond_customer, NULL);
pthread_cond_init(&cond_cooker, NULL);
for(int i = 0; i < 4; i++)
{
ret = pthread_create(&ctid, NULL, cooker, NULL);
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
}
for(int i = 0; i < 4; i++)
{
ret = pthread_create(&dtid, NULL, customer, NULL);
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
}
pthread_join(ctid, NULL);
pthread_join(dtid, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond_customer);
pthread_cond_destroy(&cond_cooker);
return 0;
}
注意事项:
一、条件变量使用过程中,条件的判断应该使用循环操作
1.四个顾客因为没有饭陷入阻塞
2.厨师做好饭,唤醒了四个顾客
3.四个顾客中,一个加锁成功吃饭,三个卡在加锁这里
4.加锁成功的顾客吃完饭唤醒厨师解锁
5.有可能抢到锁的不是厨师而是顾客
6.如果没有循环判断则会加锁成功后在没有饭时吃饭
二、条件变量使用过程中,若有多种角色则需要使用多个条件变量,不同的角色,分开等待,分开唤醒,防止唤醒角色错误
生产者与消费者模型
生产者与消费者模型:一种非常典型的设计模式。
设计模式:大佬们针对典型的应用场景设计的解决方案
应用场景:有大量数据产生以及进行处理的场景
优势:解耦合,支持忙闲不均,支持并发
实现:两种角色的线程+线程安全的队列(阻塞队列)
线程安全:
生产者与生产者:互斥
消费者与消费者:互斥
生产者与消费者:同步+互斥
线程安全的阻塞队列的实现
实例
#include <iostream>
#include <queue>
#include <pthread.h>
#define MAX_QUEUE 5
class BlockQueue
{
private:
int _capacity;//容量
std::queue<int> _queue;
pthread_mutex_t _mutex;
pthread_cond_t _cond_pro;
pthread_cond_t _cond_cus;
public:
BlockQueue(int cap = MAX_QUEUE)
: _capacity(cap)
{
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_cond_pro, NULL);
pthread_cond_init(&_cond_cus, NULL);
}
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond_pro);
pthread_cond_destroy(&_cond_cus);
}
bool Push(int data)
{
pthread_mutex_lock(&_mutex);
while(_queue.size() == _capacity)
{
pthread_cond_wait(&_cond_pro, &_mutex);
}
_queue.push(data);
pthread_cond_signal(&_cond_cus);
pthread_mutex_unlock(&_mutex);
return true;
}
bool Pop(int *data)
{
pthread_mutex_lock(&_mutex);
while(_queue.empty())
{
pthread_cond_wait(&_cond_cus, &_mutex);
}
*data = _queue.front();
_queue.pop();
pthread_cond_signal(&_cond_pro);
pthread_mutex_unlock(&_mutex);
return true;
}
};
void *productor(void *arg)
{
BlockQueue *q = (BlockQueue*)arg;
int i = 0;
while(1)
{
q->Push(i);
printf("%p-push data:%d\n", pthread_self(), i++);
}
return NULL;
}
void *customer(void *arg)
{
BlockQueue *q = (BlockQueue*)arg;
while(1)
{
int data;
q->Pop(&data);
printf("%p-get data:%d\n", pthread_self(), data);
}
return NULL;
}
int main(int argc, char *argv[])
{
BlockQueue q;
int count = 4, ret;
pthread_t ptid[4], ctid[4];
for(int i = 0; i < count; i++)
{
ret = pthread_creat(&ptid[i], NULL, productor, &q);
if(ret != 0)
{
printf("thread create error\0");
return 0;
}
}
for(int i = 0; i < count; i++)
{
ret = pthread_creat(&ctid[i], NULL, customer, &q);
if(ret != 0)
{
printf("thread create error\0");
return 0;
}
}
for(int i = 0; i < count; i++)
{
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
return 0;
}
信号量
信号量:
本质:计数器+pcb等待队列
作用:实现进程或线程间的同步与互斥
操作:
P操作:计数-1,计数<0则阻塞执行流
V操作:计数+1,唤醒一个阻塞的执行流
同步的实现:通过对计数器对资源进行计数
在获取资源之前,进行P操作;产生资源之后,进行V操作
互斥的实现:初始化值为1,表示资源只有一个
在访问资源之前进行P操作;访问完毕之后进行V操作
接口认识:
1.定义信号量:sem_t sem;
2.初始化信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value)
sem:信号量;
pshared:0-线程间/!0-进程间;
value:要设置的初值;
返回值:成功返回0;失败返回-1。
3.P操作:
int sem_wait(sem_t *sem);-阻塞
int sem_trywait(sem_t *sem);-非阻塞
int sem_timedwait(sem_t *sem, struct timespec *timeout);
4.V操作:
int sem_post(sem_t *sem);
5.销毁信号量:
int sem_destroy(sem_t *sem);
使用信号量实现一个生产者消费者模型
生产者线程流程:
1.判断能否入队数据 sem_wait(&_sem_idle)
2.(加锁)_arry[write] = data;write=(write+1)%capacity;(解锁)
3.数据节点数量+1,唤醒消费者sem_poset(&_sem_data)
消费者线程流程:
1.判断能否入队数据 sem_wait(&_sem_data)
2.(加锁)*data = _arry[read];read=(read+1)%capacity;(解锁)
3.空闲节点+1,唤醒生产者sem_poset(&_sem_idle)
#include<iostream>
#include<vector>
#include<pthread.h>
#include<semaphore.h>
#define MAX_QUEUE 5
class RingQueue
{
private:
int _capacity;
int _step_read;
int _step_write;
std::vector<int> _arry;
sem_t _sem_lock;//用信号量实现互斥
sem_t _sem_idle;//当前队列中空闲节点数量
sem_t _sem_data;//当前队列中数据节点数量
public:
RingQueue(int cap = MAX_QUEUE)
:_capacity(cap)
, _step_read(0)
, _step_write(0)
, _arry(cap)
{
sem_init(&_sem_lock, 0, 1);
sem_init(&_sem_idle, 0, cap);
sem_init(&_sem_data, 0, 0);
}
~RingQueue()
{
sem_destroy(&_sem_lock);
sem_destroy(&_sem_idle);
sem_destroy(&_sem_data);
}
bool Push(int data)
{
sem_wait(&_sem_idle);//空闲空间数量-1;<0阻塞
sem_wait(&_sem_lock);//加锁
_arry[_step_write] = data;
_step_write = (_step_write+1)%_capacity;
sem_post(&_sem_lock);//解锁
sem_post(&_sem_data);//数据空间数量—+1;唤醒消费者
return true;
}
bool Pop(int *data)
{
sem_wait(&_sem_data);
sem_wait(&_sem_lock);
*data = _arry[_step_read];
_step_read = (_step_read + 1) % _capacity;
sem_post(&_sem_lock);
sem_post(&_sem_idle);
return true;
}
};
void *productor(void *arg)
{
RingQueue *q = (RingQueue*)arg;
int i = 0;
while(1)
{
q->Push(i);
printf("%lu-push data:%d\n",pthread_self(), i++);
}
return NULL;
}
void *customer(void *arg)
{
RingQueue *q = (RingQueue*)arg;
while(1)
{
int data;
q->Pop(&data);
printf("%lu-get data:%d\n", pthread_self(), data);
}
return NULL;
}
int main(int argc, char *argv[])
{
RingQueue q;
int count = 4, ret;
pthread_t ptid[4], ctid[4];
for(int i = 0; i < count; i++)
{
ret = pthread_create(&ptid[i], NULL, productor, &q);
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
}
for(int i = 0; i < count; i++)
{
ret = pthread_create(&ctid[i], NULL, customer, &q);
if(ret != 0)
{
printf("thread create error\n");
return -1;
}
}
for(int i = 0; i < count; i++)
{
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
return 0;
}
信号量与条件变量:
条件变量的资源获取条件判断需要程序员自己进行;
信号量通过自身计数完成
条件变量需要搭配互斥锁一起使用;信号量不用
读者写者模型
应用场景:读共享,写互斥的场景
读共享:大家可以一起读(不修改只是访问)
写互斥:不能同时写,并且写的时候也不能读
读写锁:
加读锁:只要当前没人加写锁,都可以加读锁
加写锁:在既没有人读,也没有人写的时候才能加写锁
自旋锁:占据cpu资源不释放,对访问条件是否满足进行循环判断。
适用于临界资源访问操作时间较短的操作
四、线程应用
线程应用:线程池的实现;线程安全的单例模式实现;
线程池
线程池的实现:
线程池:一堆线程等着处理任务–非常典型的生产者与消费者模型应用
一堆线程(有最大数量上限)+线程安全的任务队列;
其他线程将任务抛入线程安全的任务队列中,线程池中的线程从任务队列中获取任务进行处理。
应用场景:针对大量的请求进行处理的场景(比如网络服务器…)
常规情况若要处理大量请求,使用但执行流效率较低,因此采用多执行流提高处理效率
有一个请求,则创建一个线程T1,去处理请求T2,处理完毕后线程销毁T3
若处理一个任务的总和时间中,创建与销毁线程的时间占据大比例,则意味着cpu资源大部分都消耗在线程的创建与销毁而不是处理任务
优势:线程池中的线程创建之后不销毁,而是循环取出任务进行处理,避免了频繁进行线程的创建与销毁带来的时间成本
线程池中的线程有最大数量限制,避免了峰值压力下资源耗尽,系统崩溃的风险
实现:
线程池的作用就是针对大量任务进行处理;
任务类型多种多样,但是线程入口函数是固定的;如何实现工作线程
针对不同的任务进行不同的处理?
1.线程入口函数中,分辨任务类型,调用不同的处理接口
2.其他线程在通过任务队列传入任务的同时,也把这个任务的处理方法(函数)传进来,线程池中的线程只需要使用处理方法处理任务即可,不需要关注什么样的任务如何处理。
伪代码
//定义一个函数指针
typedef void(*handler_t)(int data);
//任务节点类,线程池中的线程获取到任务节点只需要调用Run
class ThreadTask
{
int _data;//要处理的数据
handler_t _handler;//处理数据的方法(函数)
public:
void Run()
{
return _handler(_data);
}
};
class Threadpool
{
int _thr_num;//线程的数量
BlockQueue _queue;//线程安全的任务队列
void *thr_entry(void *arg)
{
ThreadTask data;
_queue.pop(&data);
data.Run();
}
public:
Init(int mq, int mt);//初始化以及线程创建
TaskPush(ThreadTask &task);//任务入队
}
线程池实现
#include <iostream>
#include <queue>
#include <pthread.h>
#define MAX_QUEUE 10
#define MAX_THREAD 5
typedef void(*handler_t)(int);
class ThreadTask
{
private:
int _data;//任务处理的数据
handler_t _handler;//数据的处理方法
public:
ThreadTask(){}
ThreadTask(int data, handler_t handler)
: _data(data)
, _handler(handler)
{
}
void SetTask(int data, handler_t handler)
{
_data = data;
_handler = handler;
}
void Run()//线程池中的线程获取节点后只需要调用run
{
return _handler(_data);
}
};
class BlockQueue
{
private:
int _capacity;
std::queue<ThreadTask> _queue;
pthread_mutex_t _mutex;
pthread_cond_t _cond_pro;
pthread_cond_t _cond_cus;
public:
BlockQueue(int cap = MAX_QUEUE)
: _capacity(cap)
{
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_cond_pro, NULL);
pthread_cond_init(&_cond_cus, NULL);
}
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond_pro);
pthread_cond_destroy(&_cond_cus);
}
bool Push(ThreadTask &data)
{
pthread_mutex_lock(&_mutex);
while(_queue.size() == _capacity)
{
pthread_cond_wait(&_cond_pro, &_mutex);
}
_queue.push(data);
pthread_cond_signal(&_cond_cus);
pthread_mutex_unlock(&_mutex);
return true;
}
bool Pop(ThreadTask *data)
{
pthread_mutex_lock(&_mutex);
while(_queue.empty())
{
pthread_cond_wait(&_cond_cus, &_mutex);
}
*data = _queue.front();
_queue.pop();
pthread_cond_signal(&_cond_pro);
pthread_mutex_unlock(&_mutex);
return true;
}
};
class ThreadPool
{
public:
ThreadPool(int thr = MAX_THREAD, int que = MAX_QUEUE)
: _max_thread(thr)
, _max_queue(que)
, _queue(que)
{
int ret;
pthread_t tid;
for(int i = 0; i < _max_thread; i++)
{
ret = pthread_create(&tid, NULL, thr_entry, (void*)this);
if(ret != 0)
{
std::cout << "thread create error\n";
exit(-1);
}
pthread_detach(tid);
}
}
// bool Init()
// {
// int ret;
// pthread_t tid;
// for(int i = 0; i < _thread_num; i++)
// {
// ret = pthread_create(&tid, NULL, thr_entry, (void*)this);
// if(ret != 0)
// {
// std::cout << "thread create error\n";
// return false;
// }
// pthread_detach(tid);
// }
// return true;
// }
bool TaskPush(ThreadTask &task)
{
_queue.Push(task);
return true;
}
private:
static void *thr_entry(void *arg)
{
ThreadPool *pool = (ThreadPool*)arg;
while(1)
{
ThreadTask task;
pool->_queue.Pop(&task);
task.Run();
}
return NULL;
}
private:
//int _thread_num;
int _max_thread;//最大线程数
int _max_queue;//最大节点数
BlockQueue _queue;
};
void test(int data)
{
printf( "i am thr:%lu-get data:%d, will sleep:%d s\n", pthread_self(), data, data%5);
sleep(data%5);
}
int main(int argc, char *argv[])
{
ThreadPool pool;
for(int i = 0; i < 10; i++)
{
ThreadTask task(i, test);
pool.TaskPush(task);
}
while(1)
sleep(1);
return 0;
}
单例模式
线程安全的单例模式:
单例模式:非常典型的一种设计模式
应用场景:一个类型只能实例化一个对象;一份资源只能被加载一次
实现:饿汉方式/懒汉方式
饿汉方式:资源在程序初始化阶段就完成加载–以空间换时间
懒汉方式:资源在使用的时候再去加载–延迟加载
饿汉方式
1.静态修饰资源
保证资源只有一份,并且在程序初始化阶段完成初始化加载
2.构造函数私有化
实现一个类只能实例化一个对象
template<class T>
class Singleton
{
private:
static T _data;
Singleton(){}
public:
T *GetInstance()
{
return &_data;
}
}
懒汉方式
懒汉方式:延迟加载
1.定义对象指针
2.static修饰
3.volatile修饰
4.加锁保护-线程安全
5.二次检测-提高效率
6.构造函数私有化
template<class T>
class Singleton
{
private:
static T *_data;
static std::mutex _mutex;
public:
volatile static T* GetInstance()
{
//防止data不为空时多余的加锁解锁
if(_data == NULL)
{
//防止多线程重复加载
_mutex.lock();
if(_data == NULL)
{
_data = new T();
}
_mutex.unlock();
}
return _data;
}
}
总结
多线程:
线程概念:1.线程是什么;2.线程的独有与共享;3.多任务多执行流处理
线程控制:1.创建;2.退出;3.等待;4.分离;
线程安全:1.线程安全是什么;2.实现;3.同步与互斥的概念
4.互斥的实现:互斥锁,互斥锁的原理,使用流程;
5.死锁,产生的必要田间,预防与避免;
6.同步的实现:条件变量-两个使用中的注意事项
7.信号量的基本使用,流程与原理
8.生产者与消费者模型:应用场景,优点,实现
线程的应用:9.线程池的基本实现与使用;10.线程安全的单例模式