详解C++11中的线程锁和条件变量

线程

std::thread类, 位于<thread>头文件,实现了线程操作。std::thread可以和普通函数和 lambda 表达式搭配使用。它还允许向线程的执行函数传递任意多参数。

#include <thread>
void func()
{
   // do some work
}
int main()
{
   std::thread t(func);
   t.join();
   return 0;
}

上面的例子中,t是一个线程实例,函数func()在该线程运行。调用join()函数是为了阻塞当前线程(此处即主线程),直到t线程执行完毕。线程函数的返回值都会被忽略,但线程函数接受任意数目的输入参数。

void func(int i, double d, const std::string& s)
{
    std::cout << i << ", " << d << ", " << s << std::endl;
}
int main()
{
   std::thread t(func, 1, 12.50, "sample");
   t.join();
   return 0;
}

虽然可以向线程函数传递任意多参数,但都必须以值传递。如果需以引用传递,则必须以std::ref或std::cref封装,如下例所示:

void func(int& a)
{
   a++;
}
int main()
{
   int a = 42;
   std::thread t(func, std::ref(a));
   t.join();
   std::stringcout << a << std::endl;
   return 0;
}

这个程序会打印43,但如果不用std::ref封装,则输出会是42。

除了join函数,这个类还提供更多的操作:

swap:交换两个线程实例的句柄

detach:允许一个线程继续独立于线程实例运行;detach 过的线程不可以再 join

int main()
{
    std::thread t(funct);
    t.detach();

    return 0;
}

一个重要的知识点是,如果一个线程函数抛出异常,并不会被常规的try-catch方法捕获。也就是说,下面的写法是不会奏效的:

try
{
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();
}
catch(const std::exception& ex)
{
    std::cout << ex.what() << std::endl;
}

要追踪线程间的异常,你可以在线程函数内捕获,暂时存储在一个稍后可以访问的结构内。

std::mutex                       g_mutex;
std::vector<std::exception_ptr>  g_exceptions;

void throw_function()
{
   throw std::exception("something wrong happened");
}

void func()
{
   try
   {
      throw_function();
   }
   catch(...)
   {
      std::lock_guard<std::mutex> lock(g_mutex);
      g_exceptions.push_back(std::current_exception());
   }
}

int main()
{
   g_exceptions.clear();

   std::thread t(func);
   t.join();

   for(auto& e : g_exceptions)
   {
      try
      {
         if(e != nullptr)
         {
            std::rethrow_exception(e);
         }
      }
      catch(const std::exception& e)
      {
         std::cout << e.what() << std::endl;
      }
   }

   return 0;
}

关于捕获和处理异常,更深入的信息可以参看Handling C++ exceptions thrown from worker thread in the main thread和How can I propagate exceptions between threads?。

此外,值得注意的是,头文件还在 `std::this_thread` 命名空间下提供了一些辅助函数:

  • get_id: 返回当前线程的 id
  • yield: 告知调度器运行其他线程,可用于当前处于繁忙的等待状态
  • sleep_for:给定时长,阻塞当前线程
  • sleep_until:阻塞当前线程至给定时间点

在上个例子中,我们需要对g_exceptions这个 vector 的访问进行同步处理,确保同一时刻只有一个线程能向它插入新的元素。为此我使用了一个 mutex 和一个锁(lock)。mutex 是同步操作的主体,在 C++ 11 的<mutex>头文件中,有四种风格的实现:

  • mutex:提供了核心的lock()unlock()方法,以及当 mutex 不可用时就会返回的非阻塞方法try_lock()
  • recursive_mutex:允许同一线程内对同一 mutex 的多重持有
  • timed_mutex: 与mutex类似,但多了try_lock_for()try_lock_until()两个方法,用于在特定时长里持有 mutex,或持有 mutex 直到某个特定时间点
  • recursive_timed_mutex:recursive_mutex和timed_mutex的结合

下面是一个使用std::mutex的例子(注意get_id()和sleep_for()两个辅助方法的使用)。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::mutex g_lock;

void func()
{
    g_lock.lock();

    std::cout << "entered thread " << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(rand() % 10));
    std::cout << "leaving thread " << std::this_thread::get_id() << std::endl;

    g_lock.unlock();
}

int main()
{
    srand((unsigned int)time(0));

    std::thread t1(func);
    std::thread t2(func);
    std::thread t3(func);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

输出如下:

entered thread 10144

leaving thread 10144

entered thread 4188

leaving thread 4188

entered thread 3424

leaving thread 3424

lock()unlock()两个方法应该很好懂,前者锁住 mutex,如果该 mutex 不可用,则阻塞线程;稍后,后者解锁线程。

下面一个例子展示了一个简单的线程安全的容器(内部使用了std::vector)。该容器提供用于添加单一元素的add()方法,以及添加多个元素的addrange()方法(内部调用add()实现)。

注意:尽管如此,下面会指出,由于va_args的使用等原因,这个容器并非真正线程安全。此外,dump()方法不应属于容器,在实际实现中它应该作为一个独立的辅助函数。这个例子的目的仅仅是展示 mutex 的相关概念,而非实现一个完整的线程安全的容器。

template <typename T>
class container
{
    std::mutex _lock;
    std::vector<T> _elements;
public:
    void add(T element)
    {
        _lock.lock();
        _elements.push_back(element);
        _lock.unlock();
    }

    void addrange(int num, ...)
    {
        va_list arguments;

        va_start(arguments, num);

        for (int i = 0; i < num; i++)
        {
            _lock.lock();
            add(va_arg(arguments, T));
            _lock.unlock();
        }

        va_end(arguments);
    }

    void dump()
    {
        _lock.lock();
        for(auto e : _elements)
            std::cout << e << std::endl;
        _lock.unlock();
    }
};

void func(container<int>& cont)
{
    cont.addrange(3, rand(), rand(), rand());
}

int main()
{
    srand((unsigned int)time(0));

    container<int> cont;

    std::thread t1(func, std::ref(cont));
    std::thread t2(func, std::ref(cont));
    std::thread t3(func, std::ref(cont));

    t1.join();
    t2.join();
    t3.join();

    cont.dump();

    return 0;
}

当你运行这个程序时,会进入死锁。原因:在 mutex 被释放前,容器尝试多次持有它,这显然不可能。这就是为什么引入std::recursive_mutex,它允许一个线程对 mutex 多重持有。允许的最大持有次数并不确定,但当达到上限时,线程锁会抛出std::system_error错误。因此,要解决上面例子的错误,除了修改addrange令其不再调用lock和unlock之外,可以用std::recursive_mutex代替mutex。

template <typename T>
class container
{
    std::recursive_mutex _lock;
    // ...
};

成功输出:

6334

18467

41

6334

18467

41

6334

18467

41

敏锐的读者可能注意到,每次调用func()输出的都是相同的数字。这是因为,seed 是线程局部量,调用srand()只会在主线程中初始化 seed,在其他工作线程中 seed 并未被初始化,所以每次得到的数字都是一样的。

手动加锁和解锁可能造成问题,比如忘记解锁或锁的次序出错,都会造成死锁。C++ 11 标准提供了若干类和函数来解决这个问题。封装类允许以 RAII 风格使用 mutex,在一个锁的生存周期内自动加锁和解锁。这些封装类包括:

lock_guard:当一个实例被创建时,会尝试持有 mutex (通过调用lock());当实例销毁时,自动释放 mutex (通过调用unlock())。不允许拷贝。

unique_lock:通用 mutex 封装类,与lock_guard不同,还支持延迟锁、计时锁、递归锁、移交锁的持有权,以及使用条件变量。不允许拷贝,但允许转移(move)。

借助这些封装类,可以把容器改写为:

template <typename T>
class container
{
    std::recursive_mutex _lock;
    std::vector<T> _elements;
public:
    void add(T element)
    {
        std::lock_guard<std::recursive_mutex> locker(_lock);
        _elements.push_back(element);
    }

    void addrange(int num, ...)
    {
        va_list arguments;

        va_start(arguments, num);

        for (int i = 0; i < num; i++)
        {
            std::lock_guard<std::recursive_mutex> locker(_lock);
            add(va_arg(arguments, T));
        }

        va_end(arguments);
    }

    void dump()
    {
        std::lock_guard<std::recursive_mutex> locker(_lock);
        for(auto e : _elements)
            std::cout << e << std::endl;
    }
};

读者可能会提出,dump()方法不更改容器的状态,应该设为 const。但如果你添加 const 关键字,会得到如下编译错误:

‘std::lock_guard<_Mutex>::lock_guard(_Mutex &)' : cannot convert parameter 1 from ‘const std::recursive_mutex' to ‘std::recursive_mutex &'

一个 mutex (不管何种风格)必须被持有和释放,这意味着lock()unlock方法必被调用,这两个方法是 non-const 的。所以,逻辑上lock_guard的声明不能是 const (若该方法 为 const,则 mutex 也为 const)。这个问题的解决办法是,将 mutex 设为mutable。mutable允许由 const 方法更改 mutex 状态。不过,这种用法仅限于隐式的,或「元(meta)」状态——譬如,运算过的高速缓存、检索完成的数据,使得下次调用能瞬间完成;或者,改变像 mutex 之类的位元,仅仅作为一个对象的实际状态的补充。

template <typename T>
class container
{
   mutable std::recursive_mutex _lock;
   std::vector<T> _elements;
public:
   void dump() const
   {
      std::lock_guard<std::recursive_mutex> locker(_lock);
      for(auto e : _elements)
         std::cout << e << std::endl;
   }
};

这些封装类锁的构造函数可以通过重载的声明来指定锁的策略。可用的策略有:

  • defer_lock_t类型的defer_lock:不持有 mutex
  • try_to_lock_t类型的try_to_lock: 尝试持有 mutex 而不阻塞线程
  • adopt_lock_t类型的adopt_lock:假定调用它的线程已持有 mutex

这些策略的声明方式如下:

struct defer_lock_t { };
struct try_to_lock_t { };
struct adopt_lock_t { };

constexpr std::defer_lock_t defer_lock = std::defer_lock_t();
constexpr std::try_to_lock_t try_to_lock = std::try_to_lock_t();
constexpr std::adopt_lock_t adopt_lock = std::adopt_lock_t();

除了这些 mutex 封装类之外,标准库还提供了两个方法用于锁住一个或多个 mutex:

lock:锁住 mutex,通过一个避免了死锁的算法(通过调用lock(),try_lock()和unlock()实现)

try_lock:尝试通过调用try_lock()来调用多个 mutex,调用次序由 mutex 的指定次序而定

下面是一个死锁案例:有一个元素容器,以及一个exchange()函数用于互换两个容器里的某个元素。为了实现线程安全,这个函数通过一个和容器关联的 mutex,对这两个容器的访问进行同步。

template <typename T>
class container
{
public:
    std::mutex _lock;
    std::set<T> _elements;

    void add(T element)
    {
        _elements.insert(element);
    }

    void remove(T element)
    {
        _elements.erase(element);
    }
};

void exchange(container<int>& cont1, container<int>& cont2, int value)
{
    cont1._lock.lock();
    std::this_thread::sleep_for(std::chrono::seconds(1)); // <-- forces context switch to simulate the deadlock
    cont2._lock.lock();    

    cont1.remove(value);
    cont2.add(value);

    cont1._lock.unlock();
    cont2._lock.unlock();
}

假如这个函数在两个线程中被调用,在其中一个线程中,一个元素被移出容器 1 而加到容器 2;在另一个线程中,它被移出容器 2 而加到容器 1。这可能导致死锁——当一个线程刚持有第一个锁,程序马上切入另一个线程的时候。

int main()
{
    srand((unsigned int)time(NULL));

    container<int> cont1;
    cont1.add(1);
    cont1.add(2);
    cont1.add(3);

    container<int> cont2;
    cont2.add(4);
    cont2.add(5);
    cont2.add(6);

    std::thread t1(exchange, std::ref(cont1), std::ref(cont2), 3);
    std::thread t2(exchange, std::ref(cont2), std::ref(cont1), 6);

    t1.join();
    t2.join();

    return 0;
}

要解决这个问题,可以使用std::lock,保证所有的锁都以不会死锁的方式被持有:

void exchange(container<int>& cont1, container<int>& cont2, int value)
{
    std::lock(cont1._lock, cont2._lock); 

    cont1.remove(value);
    cont2.add(value);

    cont1._lock.unlock();
    cont2._lock.unlock();
}

条件变量

C++ 11 提供的另一个同步机制是条件变量,用于阻塞一个或多个线程,直到接收到另一个线程的通知信号,或暂停信号,或伪唤醒信号。在<condition_variable>头文件里,有两个风格的条件变量实现:

condition_variable:所有需要等待这个条件变量的线程,必须先持有一个std::unique_lock

condition_variable_any:更通用的实现,任何满足锁的基本条件(提供lock()和unlock()功能)的类型都可以使用;在性能和系统资源占用方面可能消耗更多,因而只有在它的灵活性成为必需的情况下才应优先使用

条件变量的工作机制如下:

至少有一个线程在等待某个条件成立。等待的线程必须先持有一个unique_lock锁。这个锁被传递给wait()方法,这会释放 mutex,阻塞线程直至条件变量收到通知信号。当收到通知信号,线程唤醒,重新持有锁。

至少有一个线程在发送条件成立的通知信号。信号的发送可以用notify_one()方法, 只解锁任意一个正在等待通知信号的线程,也可以用notify_all()方法, 解锁所有等待条件成立信号的线程。

在多核处理器系统上,由于使条件唤醒完全可预测的某些复杂机制的存在,可能发生伪唤醒,即一个线程在没有别的线程发送通知信号时也会唤醒。因而,当线程唤醒时,检查条件是否成立是必要的。而且,伪唤醒可能多次发生,所以条件检查要在一个循环里进行。

下面的代码展示使用条件变量进行线程同步的实例: 几个工作员线程在运行过程中会产生错误,他们将错误码存在一个队列里。一个记录员线程处理这些错误码,将错误码从记录队列里取出并打印出来。工作员会在发生错误时,给记录员发送信号。记录员则等待条件变量的通知信号。为了避免伪唤醒,等待工作放在一个检查布尔值的循环内。

#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <queue>
#include <random>

std::mutex              g_lockprint;
std::mutex              g_lockqueue;
std::condition_variable g_queuecheck;
std::queue<int>         g_codes;
bool                    g_done;
bool                    g_notified;

void workerfunc(int id, std::mt19937& generator)
{
    // print a starting message
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "[worker " << id << "]\trunning..." << std::endl;
    }

    // simulate work
    std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));

    // simulate error
    int errorcode = id*100+1;
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout  << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;
    }

    // notify error to be logged
    {
        std::unique_lock<std::mutex> locker(g_lockqueue);
        g_codes.push(errorcode);
        g_notified = true;
        g_queuecheck.notify_one();
    }
}

void loggerfunc()
{
    // print a starting message
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "[logger]\trunning..." << std::endl;
    }

    // loop until end is signaled
    while(!g_done)
    {
        std::unique_lock<std::mutex> locker(g_lockqueue);

        while(!g_notified) // used to avoid spurious wakeups
        {
            g_queuecheck.wait(locker);
        }

        // if there are error codes in the queue process them
        while(!g_codes.empty())
        {
            std::unique_lock<std::mutex> locker(g_lockprint);
            std::cout << "[logger]\tprocessing error:  " << g_codes.front()  << std::endl;
            g_codes.pop();
        }

        g_notified = false;
    }
}

int main()
{
    // initialize a random generator
    std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count());

    // start the logger
    std::thread loggerthread(loggerfunc);

    // start the working threads
    std::vector<std::thread> threads;
    for(int i = 0; i < 5; ++i)
    {
        threads.push_back(std::thread(workerfunc, i+1, std::ref(generator)));
    }

    // work for the workers to finish
    for(auto& t : threads)
        t.join();

    // notify the logger to finish and wait for it
    g_done = true;
    loggerthread.join();

    return 0;
}

运行这个程序,输出如下(注意这个输出在每次运行下都会改变,因为每个工作员线程的工作和休眠的时间间隔是任意的):

[logger]        running...

[worker 1]      running...

[worker 2]      running...

[worker 3]      running...

[worker 4]      running...

[worker 5]      running...

[worker 1]      an error occurred: 101

[worker 2]      an error occurred: 201

[logger]        processing error:  101

[logger]        processing error:  201

[worker 5]      an error occurred: 501

[logger]        processing error:  501

[worker 3]      an error occurred: 301

[worker 4]      an error occurred: 401

[logger]        processing error:  301

[logger]        processing error:  401

上面的wait()有两个重载:

其中一个只需要传入一个unique_lock;这个重载方法释放锁,阻塞线程并将其添加到一个等待该条件变量的线程队列里;该线程在收到条件变量通知信号或伪唤醒时唤醒,这时锁被重新持有,函数返回。

另外一个在unique_lock之外,还接收一个谓词(predicate),循环直至其返回 false;这个重载可用于避免伪唤醒,其功能类似于:

while(!predicate())
      wait(lock);

于是,上面例子中布尔值g_notified可以不用,而代之以wait的接收谓词的重载,用于确认状态队列的状态(是否为空):

void workerfunc(int id, std::mt19937& generator)
{
    // print a starting message
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "[worker " << id << "]\trunning..." << std::endl;
    }

    // simulate work
    std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));

    // simulate error
    int errorcode = id*100+1;
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;
    }

    // notify error to be logged
    {
        std::unique_lock<std::mutex> locker(g_lockqueue);
        g_codes.push(errorcode);
        g_queuecheck.notify_one();
    }
}

void loggerfunc()
{
    // print a starting message
    {
        std::unique_lock<std::mutex> locker(g_lockprint);
        std::cout << "[logger]\trunning..." << std::endl;
    }

    // loop until end is signaled
    while(!g_done)
    {
        std::unique_lock<std::mutex> locker(g_lockqueue);

        g_queuecheck.wait(locker, [&](){return !g_codes.empty();});

        // if there are error codes in the queue process them
        while(!g_codes.empty())
        {
            std::unique_lock<std::mutex> locker(g_lockprint);
            std::cout << "[logger]\tprocessing error:  " << g_codes.front() << std::endl;
            g_codes.pop();
        }
    }
}

除了可重载的wait(),还有另外两个等待方法,都有类似的接收谓词以避免伪唤醒的重载方法:

wait_for:阻塞线程,直至收到条件变量通知信号,或指定时间段已过去。

wait_until:阻塞线程,直到收到条件变量通知信号,或指定时间点已达到。

这两个方法如果不传入谓词,会返回一个cv_status,告知是到达设定时间还是线程因条件变量通知信号或伪唤醒而唤醒。

标准库还提供了notify_all_at_thread_exit方法,实现了通知其他线程某个给定线程已经结束,以及销毁所有thread_local实例的机制。引入这个方法的原因是,在使用thread_local时, 等待一些通过非join()机制引入的线程可能造成错误行为,因为在等待的线程恢复或可能结束之后,他们的析构方法可能还在被调用(参看N3070和N2880)。特别的,对这个函数的一个调用,必须发生在线程刚好退出之前。下面是一个notify_all_at_thread_exit和condition_variable搭配使用来同步两个线程的实例:

std::mutex              g_lockprint;
std::mutex              g_lock;
std::condition_variable g_signal;
bool                    g_done;

void workerfunc(std::mt19937& generator)
{
   {
      std::unique_lock<std::mutex> locker(g_lockprint);
      std::cout << "worker running..." << std::endl;
   }

   std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));

   {
      std::unique_lock<std::mutex> locker(g_lockprint);
      std::cout << "worker finished..." << std::endl;
   }

   std::unique_lock<std::mutex> lock(g_lock);
   g_done = true;
   std::notify_all_at_thread_exit(g_signal, std::move(lock));
}

int main()
{
   // initialize a random generator
   std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count());

   std::cout << "main running..." << std::endl;

   std::thread worker(workerfunc, std::ref(generator));
   worker.detach();

   std::cout << "main crunching..." << std::endl;

   std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));

   {
      std::unique_lock<std::mutex> locker(g_lockprint);
      std::cout << "main waiting for worker..." << std::endl;
   }

   std::unique_lock<std::mutex> lock(g_lock);
   while(!g_done) // avoid spurious wake-ups
      g_signal.wait(lock);

   std::cout << "main finished..." << std::endl;

   return 0;
}

如果 worker 在主线程之前结束,输出如下:

main running...

worker running...

main crunching...

worker finished...

main waiting for worker...

main finished...

如果主线程在 worker 线程之前结束,输出如下:

main running...

worker running...

main crunching...

main waiting for worker...

worker finished...

main finished...

小结

<thread>头文件提供代表操作线程thread类和配套辅助方法。<mutex>头文件提供几种 mutex 互斥锁及封装类,提供多线程同步访问机制。<condition_variable>提供两种条件变量的实现,支持阻塞一个或多个线程直至接收到另一个线程发送的通知信号,或到达设定时间,或发生伪唤醒。建议读者对相关话题进行拓展阅读。

以上就是详解C++11中的线程锁和条件变量的详细内容,更多关于C++11中的线程锁和条件变量的资料请关注我们其它相关文章!

(0)

相关推荐

  • c++11新特性多线程操作实战

    c++11多线程操作 线程 thread int main() { thread t1(Test1); t1.join(); thread t2(Test2); t2.join(); thread t3 = t1; thread t4(t1); thread t5 = std::move(t1); thread t6(std::move(t1)); return 0; } t3,t4创建失败,因为thread的拷贝构造和赋值运算符重载的原型是: thread(const thread&) = d

  • c++多线程为何要使用条件变量详解

    先看示例1: #include <iostream> #include <windows.h> #include <mutex> #include<deque> #include <thread> using namespace std; int nmax = 20; std::deque<int> m_que; std::mutex mymutex; //生产者 void producterex() { int i = 1; whi

  • C++11用两个线程轮流打印整数的实现方法

    使用C++11标准的的线程语法,用两个线程轮流打印整数,一个线程打印奇数,一个线程打印偶数.可以练习线程的基本操作.线程锁和条件变量等技术.完整代码如下.代码后面附有主要语句的讲解. #include <thread> #include <iostream> #include <mutex> #include <condition_variable> std::mutex data_mutex; std::condition_variable data_va

  • 解析C++无锁队列的实现代码

    本文给出一种C++无锁队列的实现代码,主要用于一个线程读取数据另外一个线程写数据 复制代码 代码如下: #ifndef LOCK_FREE_QUEUE_H_#define LOCK_FREE_QUEUE_H_ //不加锁队列,适合一个线程读取,一个线程写#include <list>template <typename T>class LockFreeQueue{    public:        LockFreeQueue()        {             list

  • C++多线程中的锁和条件变量使用教程

    在做多线程编程时,有两个场景我们都会遇到: 多线程访问共享资源,需要用到锁: 多线程间的状态同步,这个可用的机制很多,条件变量是广泛使用的一种. 今天我用一个简单的例子来给大家介绍下锁和条件变量的使用. 代码使用C++11 示例代码 #include <iostream> #include <mutex> #include <thread> #include <condition_variable> std::mutex g_mutex; // 用到的全局锁

  • C++11 并发指南之多线程初探

    C++11 自2011年发布以来已经快两年了,之前一直没怎么关注,直到最近几个月才看了一些 C++11 的新特性,今后几篇博客我都会写一些关于 C++11 的特性,算是记录一下自己学到的东西吧,和大家共勉. 相信 Linux 程序员都用过 Pthread, 但有了 C++11 的 std::thread 以后,你可以在语言层面编写多线程程序了,直接的好处就是多线程程序的可移植性得到了很大的提高,所以作为一名 C++ 程序员,熟悉 C++11 的多线程编程方式还是很有益处的. 如果你对 C++11

  • C++实现线程池的简单方法示例

    最近自己写了一个线程池. 总的来说,线程池就是有一个任务队列,一个线程队列,线程队列不断地去取任务队列中的任务来执行,当任务队列中为空时,线程阻塞等待新的任务添加过来. 我是用queue来存放任务,vector存放thread*,然后用condition_variable 来设置线程阻塞和唤醒. 下面直接上代码吧. 线程池类头文件Thread_Pool.h /******************************************** 线程池头文件 Author:十面埋伏但莫慌 Ti

  • C++开发:为什么多线程读写shared_ptr要加锁的详细介绍

    我在<Linux 多线程服务端编程:使用 muduo C++ 网络库>第 1.9 节"再论 shared_ptr 的线程安全"中写道: (shared_ptr)的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化.根据文档(http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafety), shared_ptr 的线程

  • 详解C++11 线程休眠函数

    C++ 11之前并未提供专门的休眠函数.c语言的sleep.usleep其实都是系统提供的函数,不同的系统函数的功能还有些差异. 在Windows系统中,sleep的参数是毫秒. sleep(2*1000); //sleep for 2 seconds 在类Unix系统中,sleep()函数的单位是秒. sleep(2); //sleep for 2 seconds 从C++11开始,中C++标准库提供了专门的线程休眠函数,使得你的代码可以独立于不同的平台. std::this_thread::

  • 详解C++11中的线程锁和条件变量

    线程 std::thread类, 位于<thread>头文件,实现了线程操作.std::thread可以和普通函数和 lambda 表达式搭配使用.它还允许向线程的执行函数传递任意多参数. #include <thread> void func() { // do some work } int main() { std::thread t(func); t.join(); return 0; } 上面的例子中,t是一个线程实例,函数func()在该线程运行.调用join()函数是

  • 详解C++11中的线程库

    目录 一.线程库的介绍 1.1. 使用时的注意点 1.2. 线程函数参数 1.3. join与detach 二.原子性操作库 2.1. atomic 2.2. 锁 三.使用lambda表达式创建多个线程 四.条件变量 一.线程库的介绍 在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差.C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念.要使用标

  • 详解C++11中模板的优化问题

    1. 模板的右尖括号 在泛型编程中,模板实例化有一个非常繁琐的地方,那就是连续的两个右尖括号(>>)会被编译器解析成右移操作符,而不是模板参数表的结束.我们先来看一段关于容器遍历的代码,在创建的类模板 Base 中提供了遍历容器的操作函数 traversal(): // test.cpp #include <iostream> #include <vector> using namespace std; template <typename T> class

  • 详解Java编程中对线程的中断处理

    1. 引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时--都需要通过一个线程去取消另一个线程正在执行的任务.Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制. 如果对Java中断没有一个全面的了解,可能会误以为被中断的线程将立马退出运行,但事实并非如此.中断机制是如何工作的?捕获或检测到中断后,是抛出InterruptedException还是重设中断状态以及在方法中吞掉中断状态会有什么后果?Thread.st

  • 详解Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失

    在Spring Cloud中我们用Hystrix来实现断路器,Zuul中默认是用信号量(Hystrix默认是线程)来进行隔离的,我们可以通过配置使用线程方式隔离. 在使用线程隔离的时候,有个问题是必须要解决的,那就是在某些业务场景下通过ThreadLocal来在线程里传递数据,用信号量是没问题的,从请求进来,但后续的流程都是通一个线程. 当隔离模式为线程时,Hystrix会将请求放入Hystrix的线程池中去执行,这个时候某个请求就有A线程变成B线程了,ThreadLocal必然消失了. 下面我

  • 详解C++11中的lambda匿名函数

    目录 1. lambda匿名函数的定义 lambda匿名函数中的[外部变量] 最简单的lambda匿名函数 2. lambda匿名函数的使用 2.1 lambda匿名函数的定义和使用 2.2 值传递和引用传递的区别 2.3 执行抛出异常类型 lambda 源自希腊字母表中第 11 位的 λ,在计算机科学领域,它则被用来表示一种匿名函数.所谓匿名函数,简单地理解就是没有名称的函数,又常被称为 lambda 函数或者 lambda 表达式. 1. lambda匿名函数的定义 [capture](pa

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

  • 详解vue项目中如何引入全局sass/less变量、function、mixin

    让我们考虑下场景:当使用rem/vw开发移动端的时候,你定义了一个px转rem的函数,或者是网站配色的全局变量等,然后到工程里为每个vue文件或者组件@import 'publicfilename.scss',那得重复做这样的工作很多很多...次,万一这些公用文件目录路径变怎么办呢,哭都来不及,想想都觉得恐怖呀! 接下来拯救我们的神器就要登场了---sass-resources-loader,它可以省去重复性的引入,还支持less,postcss等,具体用法如下: npm install -d

  • 详解Java同步—线程锁和条件对象

    线程锁和条件对象 在大多数多线程应用中,都是两个及以上线程需要共享对同一数据的存取,所以有可能出现两个线程同时访问同一个资源的情况,这种情况叫做:竞争条件. 在Java中为了解决并发的数据访问问题,一般使用锁这个概念来解决. 有几种机制防止代码收到并发访问的干扰: 1.synchronized关键字(自动创建一个锁及相关的条件) 2.ReentrantLock类+Java.util.concurrent包中的lock接口(在Java5.0的时候引入) ReentrantLock的使用 publi

随机推荐