多线程编程

目录

1、学习多线程编程动力

2、力扣官网找多线程题目练习

3、解题思路

3.1、原子操作

3.2、互斥锁+条件变量

3.3、信号量

4、信号量和条件变量的关系是什么

5、其他人总结

6、打算用高版本C++重写MYDUMPER


1、学习多线程编程动力

        mydumper使用的GLib库的异步队列进行通讯,现在基本没人用这库,阅读起来不通俗易懂。打算使用C++高版本重写,可以使用多线程、原子操作、条件变量进行线程同步。

2、力扣官网找多线程题目练习

题目描述:

给你一个类:

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }

  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

两个不同的线程将会共用一个 FooBar 实例:

  • 线程 A 将会调用 foo() 方法,而
  • 线程 B 将会调用 bar() 方法

请设计修改程序,以确保 "foobar" 被输出 n 次

3、解题思路

3.1、原子操作

        你的线程需要等待某个操作完成,如果你直接用一个循环不断判断这个操作是否完成就会使得这个线程占满CPU时间,这会造成资源浪费。这时候你可以判断一次操作是否完成,如果没有完成就调用yield交出时间片,过一会儿再来判断是否完成,这样这个线程占用CPU时间会大大减少。

class FooBar {
private:
    int n;
    atomic<bool> foo_done = false;
public:
    FooBar(int n) : n(n) {}

    void foo(function<void()> printFoo) {
        for (int i = 0; i < n; i++) {
            while (foo_done) {
                this_thread::yield();
            }
            printFoo();
            foo_done = true;
        }
    }

    void bar(function<void()> printBar) {
        for (int i = 0; i < n; i++) {
            while (!foo_done) {
                this_thread::yield();
            }
            printBar();
            foo_done = false;
        }
    }
};

3.2、互斥锁+条件变量

class FooBar {
private:
    int n;
    mutex mtx;
    condition_variable cv;
    bool foo_done = false;
public:
    FooBar(int n) : n(n) {}

    void foo(function<void()> printFoo) {
        for (int i = 0; i < n; i++) {
            unique_lock<mutex> lock(mtx);
            cv.wait(lock, [&]() { return !foo_done; });
            printFoo();
            foo_done = true;
            cv.notify_one();
        }
    }

    void bar(function<void()> printBar) {
        for (int i = 0; i < n; i++) {
            unique_lock<mutex> lock(mtx);
            cv.wait(lock, [&]() { return foo_done; });
            printBar();
            foo_done = false;
            cv.notify_one();
        }
    }
};

3.3、信号量

#include <semaphore.h> // 需要手动包含信号量头文件

class FooBar {
private:
    int n;
    sem_t foo_done, bar_done;
public:
    FooBar(int n) : n(n) {
        sem_init(&foo_done, 0 , 0);
        sem_init(&bar_done, 0 , 1);
    }

    void foo(function<void()> printFoo) {
        for (int i = 0; i < n; i++) {
            sem_wait(&bar_done);
            printFoo();
            sem_post(&foo_done);
        }
    }

    void bar(function<void()> printBar) {
        for (int i = 0; i < n; i++) {
            sem_wait(&foo_done);
            printBar();
            sem_post(&bar_done);
        }
    }
};

4、信号量和条件变量的关系是什么

        信号量和互斥体确实可以解决大部分多线程并发中的问题。但是可以解决并不代表它就是最合适的解决方案。每种同步对象都有它特定的适用场景。

        多线程并发中最基本的2个需求,一个是“同步”,一个是“等待”。

        “同步”理解起来很容易,就是2个或者多个线程会同时访问同一个共享资源,这个时候就需要同步。互斥体mutex就适用于这个场景。

        “等待”最典型的场景就是“生产者/消费者”模式。如果只有互斥体mutex这种处理“同步”的设施,我们会如何处理“生产者/消费者”模式呢?很容易想到的就是“轮询”,即poll。就是每个固定的时间间隔,就检查一次是否有“消息”进来了,可以“消费”了。如果有,就“消费”;如果没有,就sleep一段时间,然后时间到了,再查询一次是否有“消息”进来了。“轮询”的缺点显而易见。如果查询间隔太短,就做太多无用功,导致程序性能下降;如果查询间隔太长,则当有“消息”进来了,可以“消费”的时候,不能及时的响应,影响用户体验。

        那么如何处理“生产者/消费者”模式最合适呢?条件变量(condition variable)就是最合适的选择。它需要配合互斥体mutex一起使用。以下以线程安全的“同步队列”为例来看一下条件变量的使用。

template<typename T>
class Sync_queue {
public:
void put(const T& val);
void put(T&& val);
void get(T& val);
private:
mutex mtx;
condition_variable cond;
list<T> q;
};
“生产者”线程可以调用它的put方法。如下:template<typename T>
void Sync_queue::put(const T& val)
{
lock_guard<mutex> lck(mtx);
q.push_back(val);
cond.notify_one();
}“消费者”线程可以调用它的get方法。如下:template<typename T>
void Sync_queue::get(T& val)
{
unique_lock<mutex> lck(mtx);
cond.wait(lck,[this]{ return !q.empty(); });
val=q.front();
q.pop_front();
}

        很明显,相比“轮询”的方法,使用“条件变量”的方法不知道高的哪里去了。

        需要说明的是,这里的“条件”可以由程序员任意指定。即,!q.empty()?

        实际上,信号量也是用来处理“等待”的。但是它有自己的特定使用场景。典型的,可以把“信号量”理解为,“可用的共享资源数”。以停车场为例,停车场的“剩余车位数”即是对应到“信号量”的value的。即:

 int sem_init(sem_t *sem, int pshared, unsigned int value);

        如果value是0,就代表已经没有剩余车位了。那么,想进停车场的车主,就需要在入口处“等待”。直到有车开走了,又有空余车位了,才可以proceed。即从“信号量”的“等待”中返回。

        可见,确实也可以使用“信号量”+“互斥体”来解决“生产者/消费者”模式的问题。但是,显然用“条件变量”更合适。

        计算机技术是一点点发展到现在的,所以有些东西在功能上面有重叠的地方是很正常的。

5、其他人总结

linux 条件变量和信号量的区别:

(1)使用条件变量可以一次唤醒所有等待者,而这个信号量没有的功能,感觉是最大区别。

(2)信号量始终有一个值(状态的),而条件变量是没有的,没有地方记录唤醒(发送信号)过多少次,也没有地方记录唤醒线程(wait返回)过多少次。从实现上来说一个信号量可以是用mutex + counter + condition variable实现的。因为信号量有一个状态,如果想精准的同步,那么信号量可能会有特殊的地方。信号量可以解决条件变量中存在的唤醒丢失问题。

(3)信号量的意图在于进程间同步,互斥锁和条件变量的意图在于线程间同步,但是信号量也可用于线程间,互斥锁和条件变量也可用于进程间。应当根据实际的情况进行决定。信号量最有用的场景是用以指明可用资源的数量。

经典的一句话:

互斥量是信号量的一种特例,互斥量的本质是一把锁。

6、打算用高版本C++重写MYDUMPER

        mydumper使用的GLib库的异步队列进行通讯。现在基本没人用这库,阅读起来也不通俗易懂。如果使用C++高版本重写的话,可以使用条件变量进行线程通讯。可运行的伪代码如下:

在centos8.0上可执行通过。编译命令:g++ -lpthread a.cpp -o a

#include <stdlib.h>
#include <iostream>
#include <pthread.h>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <unistd.h>
using namespace std;

const int THREADNUM=4;
std::atomic<int> thread_consistant_ready(0);
std::atomic<bool> dump_ready(false);

mutex mtx;
condition_variable cv;

mutex dump_mtx;
condition_variable dump_cv;

void* thread_dump(void* args){
        //thread::id this_thread_id=this_thread.get_id();
        int this_thread_id=pthread_self();
        cout<<this_thread_id<<": SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"<<endl;
        cout<<this_thread_id<<": START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT"<<endl;
        thread_consistant_ready++;
        sleep(1);
        cv.notify_one();

        unique_lock<mutex> lock(dump_mtx);
        dump_cv.wait(lock, [&]() { return dump_ready ? true : false; });
        cout<<this_thread_id<<": DUMP TABLES"<<endl;
        return 0;
}

int main(){
        cout<<"FLUSH TABLES WITH READ LOCK"<<endl;
        cout<<"SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"<<endl;
        cout<<"START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT"<<endl;
        cout<<"SHOW MASTER STATUS"<<endl;

        pthread_t tids[THREADNUM];
        for(int i =0; i<THREADNUM; i++)
        {
                void* p_args = NULL;
                int ret = pthread_create(&tids[i], NULL, thread_dump, p_args);
        }

        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [&]() { return thread_consistant_ready>=THREADNUM ?  true : false; });
        cout<<"UNLOCK TABLES"<<endl;
        dump_ready=true;
        dump_cv.notify_all();
        cout<<"COMPLETE."<<endl;
        return 0;
}

执行结果如下,可以看出主线程、辅助线程完全按预期顺序执行。

./a
FLUSH TABLES WITH READ LOCK
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT
SHOW MASTER STATUS
1271514880: SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
1271514880: START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT
1263122176: SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
1263122176: START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT
1254729472: SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
1254729472: START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT
1246336768: SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
1246336768: START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT
UNLOCK TABLES
COMPLETE.
1263122176: DUMP TABLES
1271514880: DUMP TABLES

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值