c++多线程
1. 线程库的使用
创建进程
# include<iostream>
# include<thread> //线程库头文件
using namespace std;
void thread_1()
{
cout<<"子线程1"<<endl;
}
int thread_2(int x)
{
cout<<"子线程2"<<endl;
return x;
}
int main()
{
//thread函数创建一个新线程
//first是线程名,括号里第一个参数是函数名,第二个参数是给第一个函数的参数
thread first(thread_1);
thread second(thread_2 , 100);
//若没有下面这两行,程序会报错
//因为若没有下面这两行程序会一直执行下去。
//很有可能在主线程return 0后,子线程还没有继续进行
first.detach();
second.join();
return 0;
}
join和detach
first.join(); //主进程会等待子进程first执行完毕后才继续执行
second.detach(); //主进程不会等待子进程,子线程在后台运行
joinable
判断线程是否可以join,返回bool类型
若对一个不能使用join或detach的线程强行使用join或detach会报一个system_error
所以在一个比较严谨的项目中会先判断一下是否可以使用
int main()
{
thread first(thread_1);
thread second(thread_2 , 100);
first.detach();
//second.detach();
if(second.joinable())
{
second.join();
}
cout<<"主进程";
return 0;
}
2. 互斥锁mutex(互斥量)
如下面的代码
当两个线程都对一个全局变量进行操作时候。若a = 3,两个线程恰好在同一时刻执行a++,那么a会等于4而不是我们下意识想到的5
为了避免这个情况,我们使用互斥锁mutex
当线程遇到lock()
若没上锁,则上锁,其他线程无法访问
若已上锁,则等待解锁
# include<iostream>
# include<thread>
# include<mutex>
using namespace std;
int a = 1;
mutex mtx;
void f()
{
for(int i=1;i<=1000;i++)
{
mtx.lock(); //上锁
a++;
mtx.unlock(); //解锁
}
}
int main()
{
thread t1(f);
thread t2(f);
t1.join();t2.join();
cout<<a;
return 0;
}
3. 原子操作
原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch).
讲第二个例子中的a改成atomic
atomic<int> a = 1
void f()
{
for(int i=1;i<=1000;i++)
{
a++;
}
}
int main()
{
thread t1(f);
thread t2(f);
t1.join();t2.join();
cout<<a;
return 0;
}
4. 互斥量死锁
下面的两个线程可能会发生死锁:
f1占有m1资源,f2占有m2资源,然后两个都在等待对方释放资源
mutex m1;
mutex m2;
void f1()
{
for(int i=1;i<=1000;i++)
{
m1.lock();
m2.lock();
m1.unlock();
m2.unlock();
}
}
void f2()
{
for(int i=1;i<=1000;i++)
{
m2.lock();
m1.lock();
m1.unlock();
m2.unlock();
}
}
只需要将f2改成下面这样就好了
void f2()
{
for(int i=1;i<=1000;i++)
{
m1.lock();
m2.lock();
m1.unlock();
m2.unlock();
}
}
5. unique lock
实现自动解锁
int a = 1;
mutex mtx;
void f()
{
for(int i=1;i<=1000;i++)
{
unique_lock<mutex>lg(mtx);
a++;
}
}
int main()
{
thread t1(f);
thread t2(f);
t1.join();t2.join();
cout<<a;
return 0;
}
6. 条件变量condition_variable
与互斥量搭配使用,当同一个条件变量执行notify_one或notify_all会被唤醒
condition_variable cv;
mutex mtx;
bool flag;
void wait()
{
cv.wait(mtx); //等待被唤醒就好了
cv.wait(mtx,flag); //被唤醒后如果flag为false还是会继续阻塞
}
生产者消费者:
# include<iostream>
# include<thread>
# include<mutex>
# include<string>
# include<queue>
# include<vector>
# include<functional>
# include<condition_variable>
std::queue<int> g_queue; //任务队列
std::mutex mtx;
std::condition_variable g_cv;
void Producer()
{
for(int i=0;i<10;i++) //生产者每次生产10个任务
{
std::unique_lock<std::mutex>lock(mtx); //每个任务加入队列前要上锁
g_queue.push(i);
std::cout<<"producer : "<<i<<std::endl;
g_cv.notify_one(); //加入队列后随机让一个消费者进程来取任务
}
}
void Consumer()
{
while(1) //消费者进程是一个死循环,要不断地执行任务
{
std::unique_lock<std::mutex>lock(mtx); //取任务时也要上锁
g_cv.wait(lock,[](){
return !g_queue.empty();//若任务队列为空则互斥量被唤醒也要继续等待
});
std::cout<<"consumer : " <<g_queue.front()<<std::endl;
g_queue.pop(); //将任务取出
}
}
int main()
{
std::thread t1(Producer) , t2(Consumer);
t1.join();t2.join(); //因为消费者是一个死循环,所以只需要一个线程就可以完成所有的任务
return 0;
}
7. 线程池
线程池有线程数组和任务队列
每个线程都去取任务并完成
# include<iostream>
# include<thread>
# include<mutex>
# include<string>
# include<queue>
# include<vector>
# include<functional>
# include<condition_variable>
class ThreadPool{
private:
std::vector<std::thread> threads; //线程数组
std::queue<std::function<void()>> tasks; //任务队列
std::mutex mtx;
std::condition_variable condition;
bool stop; //线程池什么时候终止
public:
ThreadPool(int numThreads) : stop(false) //构造函数,每个线程池有numThreads个线程,stop初始化为false
{
for(int i=0;i<numThreads;i++)
{
threads.emplace_back([this]{ //每个线程都是一个函数,用lambda表达式表示
while(1)
{
std::unique_lock<std::mutex>lock(mtx);
condition.wait(lock , [this]{ //线程什么时候去取任务取决于什么时候往里面加任务
return !tasks.empty() || stop; //唤醒时如果任务队列不为空或者stop为true(线程终止)的时候就继续往下走
});
if(stop && tasks.empty()) return ; //如果线程真的终止了并且任务队列为空则该线程终止执行
std::function<void()> task(std::move(tasks.front())); //将任务队列最前面的任务取出来赋值给 function<void()>类型的task
tasks.pop();
lock.unlock(); //取到任务后解锁,让其他线程去取任务
task(); //运行该任务
}
});
}
}
~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(mtx);
stop = true; //将线程池终止,因为stop是一个共享内存所以也要上锁
}
condition.notify_all(); //终止时告诉所有进程,让他们完成任务
for(auto &t : threads) //将所有任务队列的任务都完成
{
t.join();
}
}
template<class F , class... Args>
void enqueue(F&&f , Args&& ...args)
{
std::function<void()>task = std::bind(std::forward<F>(f) , std::forward<Args>(args)...);
{
std::unique_lock<std::mutex>lock(mtx);
tasks.emplace(std::move(task));
}
condition.notify_one();
}
};
int main()
{
ThreadPool pool(5);
for(int i=0;i<10;i++)
{
pool.enqueue([i]{
std::cout<<"task : "<<i<<" is running"<<std::endl;
std::cout<<"task : "<<i<<" is done"<<std::endl;
});
}
return 0;
}