[C/C++] 多线程

概念

多线程(英文:multithreading)多线程程序包含了可以并发运行的两个或更多个程序部分。这样程序中的每个部分称为一个线程,并且每个线程都定义了一个单独的执行路径

std::thread类

官方文档
1. 最简单的线程

void foo()
{
    // do something...
}
thread t1(foo);
t1.join();
thread t2(foo);
t2.join();

t1.join() 阻塞当前线程,直到t1线程执行完成才执行t2线程
2. detach 容许线程从线程句柄独立开来执行

void foo()
{
    // do something...
}
thread t1(foo);
t1.detach();
thread t2(foo);
t2.detach();

从 thread 对象分离执行的线程,允许执行独立地持续。一旦线程退出,则释放所有分配的资源。调用 detach 后, t1 不再占有任何线程。
3. std::bind()
利用std::bind()表达式绑定对象和其非静态成员函数

class Ttest
{
public:
    Ttest(){}
    ~Ttest(){}
    void Hello(int a)
    {
        printf("thread4 hello world. int a = %d\n", a);
    }

private:

};
Ttest test;
thread t4(std::bind(&Ttest::Hello, &test, 100));
printf("t4.joinable = %d\n", t4.joinable());
t4.join();
  1. Lambda表达式
thread t3([](int a, int b) {
        printf("thread3 in a=%d, int b =%d\n", a, b);
    }, 20, 22);
    t3.join();

std::future类模板

类模板std::future提供访问异步操作结果的机制。(通过 std::async 、 std::packaged_task 或 std::promise 创建的)异步操作能提供一个 std::future 对象给该异步操作的创建者。
1. std::async

int f(int x, int y)
{
    return std::pow(x, y);
}
future<int> f1 = async(launch::async, f, 100, 2);
int result1 = f1.get();
printf("result1=%d\n", result1);
  1. std::packaged_task
int f(int x, int y)
{
    return std::pow(x, y);
}
packaged_task<int(int, int)> task(f);
future<int> result = task.get_future();
thread tf(move(task), 2, 10);
tf.join();
printf("ft(f) : %d\n", result.get());
  1. std::promise
int f(int x, int y)
{
    return pow(x, y);
}
void f_wrapper(promise<int> p, int a, int b)
{
    p.set_value(f(a, b));
}
promise<int> p1;
future<int> f11 = p1.get_future();
thread tp1(f_wrapper, move(p1), 100, 2);
tp1.join();
int result2 = f11.get();
printf("result2=%d\n", result2);

互斥对象mutex和锁lock

mutex类用于保护共享数据免受从多个线程同时访问。
注意:通常不直接使用 std::mutex : std::unique_lock 、 std::lock_guard 或 std::scoped_lock (C++17 起)以更加异常安全的方式管理锁定。
1. std::lock_guard

class  LogFile
{
public:
    LogFile()
    {
        f_.open("log.txt");
    }

    void shared_print(string s, int value)
    {
        lock_guard guard(mu_);
        f_ << "From " << s << " : " << value << endl;
    }

private:
    mutex mu_;
    ofstream f_;

};

void print(LogFile &log)
{
    for (int i = 0; i > -100; i--)
    {
        log.shared_print("print", i);
    }
}

LogFile log;
thread t100(print, std::ref(log));
for (int i = 0; i < 100; i++)
    log.shared_print("From main", i);
t100.join();

死锁和解决办法

class  LogFile
{
public:
    LogFile()
    {
        f_.open("log.txt");
    }

    void shared_print(string s, int value)
    {
        lock_guard guard(mu_);
        lock_guard guard2(mu2_);
        cout << "From " << s << " : " << value << endl;
    }
    void shared_print2(string s, int value)
    {
        lock_guard guard2(mu2_);
        lock_guard guard(mu_);
        cout << "From " << s << " : " << value << endl;
    }

private:
    mutex mu_;
    mutex mu2_;
    ofstream f_;

};
void print(LogFile &log)
{
    for (int i = 0; i > -100; i--)
    {
        log.shared_print("print", i);
    }
}
LogFile log;
thread t100(print, std::ref(log));
for (int i = 0; i < 100; i++)
    log.shared_print2("From main", i);
t100.join();

死锁的解决方式1:
- shared_print1和shared_print2函数内,guard1,guard2声明顺序保证相同

void shared_print(string s, int value)
    {
        lock_guard guard(mu_);
        lock_guard guard2(mu2_);
        cout << "From " << s << " : " << value << endl;
    }
    void shared_print2(string s, int value)
    {
        lock_guard guard(mu_);
        lock_guard guard2(mu2_);
        cout << "From " << s << " : " << value << endl;
    }
  • std::lock(guard1, guard2)
void shared_print(string s, int value)
    {
        std::lock(mu_, mu2_);
        lock_guard guard(mu_, adopt_lock);
        lock_guard guard2(mu2_, adopt_lock);
        cout << "From " << s << " : " << value << endl;
    }
    void shared_print2(string s, int value)
    {
        std::lock(mu_, mu2_);
        lock_guard guard2(mu2_, adopt_lock);
        lock_guard guard(mu_, adopt_lock);
        cout << "From " << s << " : " << value << endl;
    }
  1. std::unique_lock
    类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
    • std::defer_lock 捕获的互斥所有权
    • std::try_to_lock 尝试获得互斥的所有权而不阻塞
    • std::adopt_lock 假设调用方线程已拥有互斥的所有权
class  LogFile
{
public:
    LogFile()
    {
        f_.open("log.txt");
    }

    void shared_print(string s, int value)
    {
        unique_lock lock(mu_, defer_lock);
        lock.lock();
        cout << "From " << s << " : " << value << endl;
        lock.unlock();
    }

private:
    mutex mu_;
    ofstream f_;

};

void print(LogFile &log)
{
    for (int i = 0; i > -100; i--)
    {
        log.shared_print("print", i);
    }
}
LogFile log;
thread t100(print, std::ref(log));
for (int i = 0; i < 100; i++)
    log.shared_print("From main", i);
t100.join();

注意:

unique_lock拥有更加灵活、丰富的功能,但也非常消耗资源,一般功能简单的情况下请使用lock_guard

std::call_once

准确执行一次可调用 (Callable) 对象 f ,即使同时从多个线程调用。
若在调用 call_once 的时刻, flag 指示已经调用了 f ,则 call_once 立即返回(称这种对 call_once 的调用为消极)。

class  LogFile
{
public:
    LogFile()
    {
    }

    void shared_print(string s, int value)
    {
        call_once(flag1_, [&]() {
            f_.open("log.txt");
        }); // 仅仅调用一次
        unique_lock lock(mu_, defer_lock);
        lock.lock();
        cout << "From " << s << " : " << value << endl;
        lock.unlock();
    }

private:
    mutex mu_;
    ofstream f_;
    once_flag flag1_;

};

std::condition_variable

condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。
有意修改变量的线程必须:
- 获得 std::mutex (典型地通过 std::unique_lock )
- 在保有锁时进行修改
- 在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)

mutex mu_;
condition_variable con_;
deque<int> de_;

void function_1()
{
    int count = 10;
    while (count > 0)
    {
        unique_lock locker(mu_);
        de_.push_back(count);
        locker.unlock();
        con_.notify_one();
        std::this_thread::sleep_for(chrono::seconds(1));
        count--;
    }
}

void function_2()
{
    int data = 0;
    while (data != 1)
    {
        unique_lock locker(mu_);
        con_.wait(locker, []() {return !de_.empty(); });
        data = de_.back();
        de_.pop_back();
        locker.unlock();
        cout << "t2 got a value from t1 : " << data << endl;
    }
}
thread t101(function_1);
thread t102(function_2);
t101.join();
t102.join();

时间限制函数

// 线程的时间限制
    thread t103(f, 1, 2);
    this_thread::sleep_for(chrono::milliseconds(3));
    chrono::steady_clock::time_point tp = chrono::steady_clock::now()
        + chrono::milliseconds(4);
    this_thread::sleep_until(tp);

    // 锁的时间限制
    mutex mu;
    unique_lock<mutex> locker(mu);
    locker.try_lock_for(chrono::milliseconds(3));
    locker.try_lock_until(tp);

    // 条件变量的时间限制
    std::condition_variable cond;
    cond.wait_for(locker, chrono::milliseconds(3));
    cond.wait_until(locker, tp);

    // future时间限制
    std::promise<int> p;
    std::future<int> f = p.get_future();
    f.wait_for(chrono::milliseconds(3));
    f.wait_until(tp);

八种方式创建子线程

class A
{
public:
    void f(int x, int y) {}
    int operator()(int n) { return 0; }
};

void foo(int a)
{
}
A a;
thread t1(a, 6);  // 传递a的拷贝给子线程
thread t2(ref(a), 6); // 传递a的引用给子线程
thread t3(move(a), 6); // a在主线程中将不再有效
thread t4(A(), 6);  // 传递临时创建的a对象给子线程

thread t5(foo, 6);
thread t6([](int x) {return x * x; }, 6);

thread t7(&A::f, a, 8, 'w'); // 传递a的拷贝的成员函数给子线程
thread t8(&A::f, &a, 8, 'w'); // 传递a的地址的成员函数给子线程

你可能感兴趣的:(C/C++)