并发:指两个或多个事件在同一个时间段内发生。----单核CPU,并发执行
并行:指两个或多个事件在同一时刻发生(同时发生)—多核CPU,并行执行
进程:内存中运行的程序,每个进程都用独立的内存空间,一个应用程序有多个进程。
线程Thread:程序执行流的最小单元,线程不会执行程序,其就像载体,将要执行的代码运送到cpu进行处理。
多线程就是多个线程同时并发执行。
分时调度:轮流使用cpu,平均分配。
抢占式调度:先优先级高的使用,如果同级,随机选择。
a、设置线程的优先级。
b、多线程程序并不能提高程序的运行速度,但可以提高运行效率,是的cpu使用效率更高。
线程是一个进程内部的控制序列。
避免拥塞、避免CPU的空转–异步调用、提升效率,C++11提供了语言层面上的多线程,包含在头文件中。
它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。
C++11 新标准中引入了5个头文件来支持多线程编程,如下图所示:
进程是系统进行资源分配和调用的一个独立单位
线程是进程的一个实体,是CPU调度和分派的基本单位,线程只是一段程序的执行。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
int pthread_create(获取线程id, 设置线程属性, 线程运行函数地址, 运行函数传参)
举例:
#include
#include
#include
// 使用线程时需要添加这个头文件
// myfunc 线程携带的函数;固定为Void*型
myfunc(void* args){
printf("hello world\n");
return NULL;
}
int main(){
pthread_t th1; // 声明线程th1
pthread_create(&th1,NULL,(void*)myfunc,NULL); //创建线程并说明需要执行的函数
pthread_join(th1,NULL); //线程阻塞
}
案例中的main函数就是所谓的主线程,main函数结束即主线程结束,主线程结束就是进程结束,进程一旦结束所有的线程就都结束了。
创建线程时传递参数,由于创建线程可以传递一个void指针,因此只需把要传递的数据的首地址给线程入口函数即可,但在线程入口函数中需要把void指针转换成对应的类型才能使用。
在线程执行期间,线程之间共享的数据要保证有效,否可能会有段错误、脏数据。
int pthread_join(pthread_t thread, void **retval);
Pthread_join(等待的线程id, 接受线程函数的返回值)。
注意:
1)一旦调用就会进入阻塞,会等待线程结束才返回,如果等待的线程不存在,就立即返回。
2)在等待线程结束时可以获取到线程入口函数的返回值,返回值是指针,因此需要使用二级指针来接收,当线程时要保证线程返回的指针指向的数据依然有效。
3)如果线程返回的指针指向的是堆内存,则接收到该指针后要进行释放。
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);
功能:是比较两人个线程ID是不是同一个线程,原因有些系统的pthead_t是以结构体形式实现的。
1)、从线程的入口函数中return
2)、调用pthread_exit函数
void pthread_exit(void *retval);
功能:立即结束当前线程,如果在主线程中直接结束,相当于结束整个进程。retval:给pthread_join函数的返回值,相当于线程入口函数的return。
exit功能是让进程结束,进程中的任何一个线程调用exit都会导致整个进程结束。
同步方式:创建线程之后,主线程可以调用pthread_join函数等待。
异步方式:创建线程之后,就和主线程没有关系,不能被等待。
默认创建出的线程是同步方式执行,可以调用pthread_detach函数使线程分离。
int pthread_detach(pthread_t thread);
int pthread_detach(被分离的线程ID);
取消线程就是主线程或父线程向子线程发送结束请求。
int pthread_cancel(pthread_t thread);
第一种:
std::thread myThread ( thread_fun);//函数形式为void thread_fun()
myThread.join();
第二种:
std::thread myThread ( thread_fun(100));
myThread.join();
第三种:
std::thread (thread_fun,1).detach();//直接创建线程,没有名字
举例1:
#include
#include
using namespace std;
void thread_1()
{
cout<<"first_sub_thread"<<endl;
}
void thread_2(int x)
{
cout<<"x:"<<x<<endl;
cout<<"second_sub_thread"<<endl;
}
int main()
{
thread first ( thread_1); // 开启线程,调用:thread_1()
thread second (thread_2,100); // 开启线程,调用:thread_2(100)
thread third(thread_2,3); //开启第3个线程,共享thread_2函数。
std::cout << "main_thread\n";
first.join(); //必须说明添加线程的方式
second.join();
third.join();
std::cout << "子线程结束.\n"; //必须join完成
return 0;
}
当线程启动后,一定要在和线程相关联的thread销毁前,确定以何种方式等待线程执行结束。比如上例中的join。
detach方式:启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束主线程不会等待子线程结束。如主线程运行结束,程序则结束。
join方式:等待启动的线程完成,才会继续往下执行join后的代码不会被执行,除非子线程结束。
可以使用joinable判断是join模式还是detach模式
if (myThread.joinable()) mythread.join();
有4个功能函数,具体如下:
std::this_thread::get_id() // 获取线程id
std::this_thread::yield() // 放弃线程执行,回到就绪状态
std::this_thread::sleep_for(std::chrono::seconds(1)); // 暂停1秒
std::this_thread::sleep_until(system_clock::from_time_t(mktime™)); // 直到规定分钟后执行
mutex头文件声明了与互斥量(mutex)相关的类。mutex提供了4种互斥类型,如下所示:
std::mutex // 最基本的 Mutex 类。
std::recursive_mutex // 递归 Mutex 类。
std::time_mutex // 定时 Mutex 类。
std::recursive_timed_mutex // 定时递归 Mutex 类。
mutex常用操作:
lock():资源上锁
unlock():解锁资源
trylock():查看是否上锁,它有下列3种类情况:
(1)未上锁返回false,并锁住;
(2)其他线程已经上锁,返回true;
(3)同一个线程已经对它上锁,将会产生死锁。
创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。
lock_guard的特点:
创建即加锁,作用域结束自动析构并解锁,无需手工解锁
不能中途解锁,必须等作用域结束才解锁
不能复制
#include
#include
#include
int g_i = 0;
std::mutex g_i_mutex; // protects g_i,用来保护g_i
void safe_increment()
{
const std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
// g_i_mutex自动解锁
}
int main()
{
std::cout << "main id: " <<std::this_thread::get_id()<<std::endl;
std::cout << "main: " << g_i << '\n';
std::thread t1(safe_increment);
std::thread t2(safe_increment);
t1.join();
t2.join();
std::cout << "main: " << g_i << '\n';
}
简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。
unique_lock的特点:
创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定
可以随时加锁解锁
作用域规则同 lock_grard,析构时自动释放锁
不可复制,可移动
条件变量需要该类型的锁作为参数(此时必须使用unique_lock)
所有 lock_guard 能够做到的事情,都可以使用 unique_lock 做到,反之则不然。那么何时使lock_guard呢?很简单,需要使用锁的时候,首先考虑使用 lock_guard,因为lock_guard是最简单的锁。
#include
#include
#include
struct Box {
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
void transfer(Box &from, Box &to, int num)
{
// defer_lock表示暂时unlock,默认自动加锁
std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
//两个同时加锁
std::lock(lock1, lock2);//或者使用lock1.lock()
from.num_things -= num;
to.num_things += num;
//作用域结束自动解锁,也可以使用lock1.unlock()手动解锁
}
int main()
{
Box acc1(100);
Box acc2(50);
std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
t1.join();
t2.join();
std::cout << "acc1 num_things: " << acc1.num_things << std::endl;
std::cout << "acc2 num_things: " << acc2.num_things << std::endl;
}
condition_variable 的头文件有两个variable类,一个是 condition_variable,另一个是 condition_variable_any。
condition_variable 必须结合unique_lock使用。condition_variable_any 可以使用任何的锁。下面以 condition_variable 为例进行介绍。
condition_variable 条件变量可以阻塞(wait、wait_for、wait_until)调用的线程直到使用(notify_one或notify_all)通知恢复为止。
condition_variable 是一个类,这个类既有构造函数也有析构函数,使用时需要构造对应的 condition_variable 对象,调用对象相应的函数来实现上面的功能。
类型 说明
wait // Wait until notified
wait_for // Wait for timeout or until notified
wait_until // Wait until notified or time point
notify_one // 解锁一个线程,如果有多个,则未知哪个线程执行
notify_all // 解锁所有线程
死锁:th1运行后,th2等待th1解锁,运行出错,th1未执行完,重新启动,th1又重新执行,th2排在th1的前面,需要等th1计算所,th1等th2结束,相互等待,形成死锁。
锁的使用方法
pthread_mutex_t lock; 声明一个锁
pthread_mutex_init(&lock,NULL); 对声明的锁进行初始化
pthread_mutex_lock(&lock); //上锁 此时其他线程就开始等待
pthread_mutex_lock(&unlock); //解锁 其他线程可以使用资源了
在一个程序中,如果我们需要多次使用线程,这就意味着,需要多次的创建并销毁线程。而创建并销毁线程的过程势必会消耗内存,线程过多会带来调动的开销,进而影响缓存局部性和整体性能。
因为程序边运行边创建线程是比较耗时的,所以我们通过池化的思想:在程序开始运行前创建多个线程,这样,程序在运行时,只需要从线程池中拿来用就可以了.大大提高了程序运行效率.
一般线程池都会有以下几个部分构成:
线程池管理器(ThreadPoolManager):用于创建并管理线程池,也就是线程池类
工作线程(WorkThread): 线程池中线程
任务队列task: 用于存放没有处理的任务。提供一种缓冲机制。
append:用于添加任务的接口
线程池实现代码:
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include
#include
#include
#include
#include
#include
#include //unique_ptr
#include
const int MAX_THREADS = 1000; //最大线程数目
template <typename T>
class threadPool
{
public:
threadPool(int number = 1);//默认开一个线程
~threadPool();
std::queue<T *> tasks_queue; //任务队列
bool append(T *request);//往请求队列<task_queue>中添加任务
private:
//工作线程需要运行的函数,不断的从任务队列中取出并执行
static void *worker(void *arg);
void run();
private:
std::vector<std::thread> work_threads; //工作线程
std::mutex queue_mutex;
std::condition_variable condition; //必须与unique_lock配合使用
bool stop;
};//end class
//构造函数,创建线程
template <typename T>
threadPool<T>::threadPool(int number) : stop(false)
{
if (number <= 0 || number > MAX_THREADS)
throw std::exception();
for (int i = 0; i < number; i++)
{
std::cout << "created Thread num is : " << i <<std::endl;
work_threads.emplace_back(worker, this);//添加线程--直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
}
}
template <typename T>
inline threadPool<T>::~threadPool()
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
condition.notify_all();
for (auto &ww : work_threads)
ww.join();//可以在析构函数中join
}
//添加任务
template <typename T>
bool threadPool<T>::append(T *request)
{
/*操作工作队列时一定要加锁,因为他被所有线程共享*/
queue_mutex.lock();//同一个类的锁
tasks_queue.push(request);
queue_mutex.unlock();
condition.notify_one(); //线程池添加进去了任务,自然要通知等待的线程
return true;
}
//单个线程
template <typename T>
void *threadPool<T>::worker(void *arg)
{
threadPool *pool = (threadPool *)arg;
pool->run();//线程运行
return pool;
}
template <typename T>
void threadPool<T>::run()
{
while (!stop)
{
std::unique_lock<std::mutex> lk(this->queue_mutex);
/* unique_lock() 出作用域会自动解锁 */
this->condition.wait(lk, [this] { return !this->tasks_queue.empty(); });
//如果任务为空,则wait,就停下来等待唤醒
//需要有任务,才启动该线程,不然就休眠
if (this->tasks_queue.empty())//任务为空,双重保障
{
assert(0&&"断了");//实际上不会运行到这一步,因为任务为空,wait就休眠了。
continue;
}
else
{
T *request = tasks_queue.front();
tasks_queue.pop();
if (request)//来任务了,开始执行
request->process();
}
}
}
#endif
#include "mythread.h"
#include
#include
using namespace std;
class Task
{
public:
void process()
{
//cout << "run........." << endl;
//测试任务数量
long i=1000000;
while(i!=0)
{
int j = sqrt(i);
i--;
}
}
};
int main(void)
{
threadPool<Task> pool(6);//6个线程,vector
std::string str;
while (1)
{
Task *tt = new Task();
//使用智能指针
pool.append(tt);//不停的添加任务,任务是队列queue,因为只有固定的线程数
cout<<"添加的任务数量: "<<pool.tasks_queue.size()<<endl;;
delete tt;
}
}