|
从现在开始,我们将会进入线程通信的阶段。我们主要关注三大问题:
1、一个线程如何把消息传递给另外一个
2、确保两个及以上的线程不会在关键位置出现交叉
3、如何保证线程间有一个正确的顺序
一、massage pass
1、可以采用全局变量的方法。这种方法比较危险,容易发生冲突,在使用时要配合同步机制
2、可以采用promise、future语法
基本用法:promise<T>对象通过promise.get_future()函数和future<T>对象联系起来,promise对象通过set_value设置值,而future对象通过get获取值。这在两个不同线程内也可以执行。在get没有获取值值之前该线程被阻塞。这里的T是任意类。
#include <iostream>
#include <future>
#include <chrono>
void Thread_Fun1(std::promise<int> &p)
{
//为了突出效果,可以使线程休眠5s
std::this_thread::sleep_for(std::chrono::seconds(5));
int iVal = 233;
std::cout << &#34;传入数据(int):&#34; << iVal << std::endl;
//传入数据iVal
p.set_value(iVal);
}
void Thread_Fun2(std::future<int> &f)
{
//阻塞函数,直到收到相关联的std::promise对象传入的数据
auto iVal = f.get(); //iVal = 233
std::cout << &#34;收到数据(int):&#34; << iVal << std::endl;
}
int main()
{
//声明一个std::promise对象pr1,其保存的值类型为int
std::promise<int> pr1;
//声明一个std::future对象fu1,并通过std::promise的get_future()函数与pr1绑定
std::future<int> fu1 = pr1.get_future();
//创建一个线程t1,将函数Thread_Fun1及对象pr1放在线程里面执行
std::thread t1(Thread_Fun1, std::ref(pr1));
//创建一个线程t2,将函数Thread_Fun2及对象fu1放在线程里面执行
std::thread t2(Thread_Fun2, std::ref(fu1));
//阻塞至线程结束
t1.join();
t2.join();
return 1;
}3、可以采用packaged_task语法
packaged_task同样和future搭配使用。在上文中promise是将一个值传递给future类型的对象。现在packaged_task则是将一个函数包装起来,并将其返回值传回future,进而实现了线程之间的通信。
#include<iostream>
#include<future>
#include<thread>
int TripleX_func(int x){
x = x*3;
std::cout<<&#34;3X thread id:&#34;<<std::endl;
std::cout<<std::this_thread::get_id()<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
return x;
}
int main(){
// 将函数(某种操作后的值)打包起来
// std::packaged_task<函数返回类型(参数类型)> 变量名(函数名)
std::packaged_task<int(int)> pt{TripleX_func};
//并将结果返回给future,类型是int
std::future<int> fu = pt.get_future();
//future提供了一些函数比如get(),wait(),wait_for()。
//一般用get()来获取future所得到的结果
//如果异步操作还没有结束,那么会在此等待异步操作的结束,并获取返回的结果。
std::thread t(std::ref(pt), 5);
std::cout<<fu.get()<<std::endl;
//输出3X线程和main线程的id,可以发现是两个不同的ID。
std::cout<<&#34;main thread id:&#34;<<std::endl;
std::cout<<std::this_thread::get_id()<<std::endl;
t.join();
return 0;
}二、如何防止关键位置不会出现线程交叉
1、锁(mutex)
在头文件<mutex>中,是一个最基本的锁。创建语法
mutex mtx;//创建锁对象
其中有函数lock()和unlock()。lock()函数代表申请到锁并上锁,unlock()表示解锁并释放锁。lock()在申请锁时若发现该锁处于上锁状态,则该线程则处于阻塞,直到开锁并竞争得到锁就可以运行临界区。
try_lock():该函数用于尝试得到锁,得到锁则上锁并返回值true,没有得到锁则返会false,但不会阻塞线程。
2、shared_mutex
该类型存在两种状态:共享锁和独占锁
具体的使用方法可以参考我的另一篇文章:
3、atomic
原子变量在内部是使用了自旋锁,对每一个访问该变量的线程自动加锁,使得访问安全。
atomic<T> a;//原子类型的定义还有一些简单的写法记号如下

简写记号
//示例:
//C++11
atomic_int total{0};
void func()
{
for (int i = 0; i < 100000; i++)
{
total += i;
}
}
int main()
{
thread th1(func);
thread th2(func);
th1.join();
th2.join();
cout<<&#34;total:&#34;<<total<<endl;
return 0;
}三、如何确保线程运行有一个正确的顺序
1、一些经典的函数
1)join()函数:某个线程使用join函数,那么父线程阻塞直到子线程结束。
2)detach()函数:某个线程使用detach函数,那么父线程不会等待子线程结束才继续工作,而是以一种异步的方式继续工作,直到结束。注意:如果父线程已经结束而子线程没有结束,detach也许不会释放占用的资源
2、条件变量
详解见:
condition_variable:条件变量通常和mutex一起使用,以达到阻止一个或者多个线程运行的,知道条件变量发生变化为止的目的。
内部方法:
1、wait:需要参数unique_lock<mutex>,注意,没有上锁的mutex传入是非法行为。该函数首先将该线程放入等待队列,进而释放锁,随即运行调度程序,最后尝试获取锁。
2、wait(unique_lock(mutex) mtx, pred):注意这里的pred是一个谓词,可以是函数或者lambda。在不满足谓词时会重新运行wait程序。该方法等价于:
while(!pred)
{
wait(lock);
}
3、notify_one:挑选一个线程运行,这里的步骤不会加锁,即唤醒因锁而阻塞的线程,该线程会顺利获取到锁。
4、notify_all:将所有线程在锁上阻塞的线程唤醒,以抢占的方式竞争锁。
实例:
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock lk(m);
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << &#34;Worker thread is processing data\n&#34;;
data += &#34; after processing&#34;;
// Send data back to main()
processed = true;
std::cout << &#34;Worker thread signals data processing completed\n&#34;;
// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = &#34;Example data&#34;;
// send data to the worker thread
{
//注意lock_guard是在作用域内自动上锁和解锁
std::lock_guard lk(m);
ready = true;
std::cout << &#34;main() signals data ready for processing\n&#34;;
}//自动解锁
cv.notify_one();
// wait for the worker
{
std::unique_lock lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << &#34;Back in main(), data = &#34; << data << &#39;\n&#39;;
worker.join();
} |
|