查看: 95|回复: 0

多线程学习c++:线程通信

[复制链接]

4

主题

5

帖子

13

积分

新手上路

Rank: 1

积分
13
发表于 2023-4-10 08:47:23 | 显示全部楼层 |阅读模式
从现在开始,我们将会进入线程通信的阶段。我们主要关注三大问题:
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 << "传入数据(int):" << 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 << "收到数据(int):" << 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<<"3X thread id:"<<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<<"main thread id:"<<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<<"total:"<<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 << "Worker thread is processing data\n";
    data += " after processing";

    // Send data back to main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";

    // 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 = "Example data";
    // send data to the worker thread
    {
   //注意lock_guard是在作用域内自动上锁和解锁
        std::lock_guard lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }//自动解锁
    cv.notify_one();

    // wait for the worker
    {
        std::unique_lock lk(m);
        cv.wait(lk, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';

    worker.join();
}
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表