C++ 11之前,C++语言并没有提供支持,想要开发多线程程序就要借助于操作系统提供的多线程接口,但是,这样并不能开发跨平台可移植的并发程序,C++11提供了多线程语言支撑,使得程序的可移植性大大提升。
1 线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。具备占用并发、占用资源少、资源共享等优势。同样,在使用线程进行编码时也要关注多线程的一些缺点,如:变量共享导致的结果差异、多线程调试、死锁等很多现实的问题,因此在使用多线程编码时要格外注意。
1.1 创建线程
C++ 11中创建一个线程是很简单的事情,只需要使用std::thread就可以轻松创建一个线程,我们要做的只是提供一个线程函数或者函数对象,创建线程时也可以同时给线程函数指定参数,代码如下:
void foo()
{
std::cout<<"foo"<<std::endl;
}
void bar(int x)
{
std::cout<<"bar::x"<<x<<std::endl;
}
int main()
{
std::thread first (foo);
std::thread second (bar,0);
std::cout << "main, foo and bar now execute concurrently...\n";
first.join();
second.join();
std::cout << "foo and bar completed.\n";
return 0;
}上面的代码创建了两个线程,执行的线程函数一个有参一个无参,线程运行后输出的结果是这样的:
main, foo and bar now execute concurrently...
foo
bar::x=0
foo and bar completed.但是也可能有其他输出形式,原因先不做解释,后面会揭晓答案。在这个代码中,创建的线程对象分别调用了join方法,这个方法的作用是阻塞线程,直到线程函数执行完毕,如果线程函数有返回值,返回值将会被忽略。
在thread中除了join外也提供了另外一个方法:detach,线程创建完成后,调用detach方法,线程就会和主线程进行分离,编程一个后台的线程去执行,主线程也不会被阻塞。需要关注的是一旦分离,两个线程之间就不会再有关联,也不能在通过join等待线程函数执行完毕。使用方法如下:
void foo()
{
while(1)
{
std::cout<<"foo"<<std::endl;
Sleep(1000);
}
}
int main()
{
std::thread first (foo);
while(1)
{
std::cout << "main thread.\n";
Sleep(1000);
}
first.detach();
return 0;
}上面的代码线程创建完毕后,主线程输出“main thread”。线程first就会和主线程脱离,在后台执行线程函数,相互交叉打印日志。
按照上面的方法创建线程是一件非常简单的事情,但是,也有弊端,既:std::thread如果在线程函数返回前被析构就会发生意想不到的错误,因此需要确保线程函数在线程被析构之前执行完毕。如:可以将线程保存到一个容器中。如:
void foo()
{
std::cout<<"foo"<<std::endl;
}
void bar(int x)
{
std::cout<<"bar::x="<<x<<std::endl;
}
std::vector<std::thread> g_list;
std::vector<std::shared_ptr<std::thread>> g_list2;
void CreateThread()
{
//std::thread t(foo);
// g_list.push_back(std::move(t));
g_list2.push_back(std::make_shared<std::thread>(bar,2));
}
int main()
{
CreateThread();
for(auto &thread : g_list)
{
thread.join();
}
for(auto &thread : g_list2)
{
thread->join();
}
return 0;
}1.2 线程基本用法
1) 获取当前信息:获取线程ID和CPU核心数
void foo()
{
}
int main()
{
std::thread first (foo);
//获取线程ID
std::cout<<first.get_id()<<std::endl;
//获取CPU核心数
std::cout<<std::thread::hardware_concurrency()<<std::endl;
return 0;
}2)线程休眠
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout<<"time out"<<std::endl;
}
int main()
{
std::thread first (foo);
first.join();
return 0;
}2 互斥量
互斥量是一种线程同步的手段,用来保护多线程同时访问的共享数据,在C++ 11中,提供了多种互斥量,如下:
2.1 独占互斥量
互斥量通常借助lock方法阻塞线程,取得控制权执行完毕后再调用unlock进行释放,在这个过程中,lock和unlock需要成对出现,这种方式时候同步的,同样也有一种方法是异步的,try_lock,取得互斥锁后会返回true,如果没有取得则返回false,是非阻塞的,std::mutex的用法如下:
std::mutex g_lock;
void foo()
{
g_lock.lock();
std::cout<<"entry thread and get lock"<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"leave thread and release lock"<<std::endl;
g_lock.unlock();
}
int main()
{
std::thread first (foo);
std::thread second (foo);
std::thread three (foo);
first.join();
second.join();
three.join();
return 0;
}上面的代码通过互斥锁实现了三个线程的资源共享,运行结果如下:
entry thread and get lock
leave thread and release lock
entry thread and get lock
leave thread and release lock
entry thread and get lock
leave thread and release locklock和unlock必须成对出现,如果缺少unlock可能会造成第一个线程获得锁资源后,不进行释放,导致后面的线程产生所等待,导致线程假死。为了防止这种现像产生,可以使用lock_guard进行简化,他会在构造时获得锁资源,超出生命周期后对锁资源进行释放。如:
std::mutex g_lock;
void foo()
{
std::lock_guard<std::mutex> locker(g_lock);
std::cout<<"entry thread and get lock"<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"leave thread and release lock"<<std::endl;
}2.2 递归的独占互斥变量
递归锁一般不推荐使用,主要原因如下:
因此,鉴于以上原因,实际编码过程中并不建议使用递归锁,这里为了演示递归锁的使用方法,举例代码如下:
struct Complex{
std::recursive_mutex mutex;
int i;
Complex():i(2){};
void mul(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
std::cout<<"mul::i="<<i<<std::endl;
}
void div(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i /= x;
std::cout<<"div::i="<<i<<std::endl;
}
void both(int x,int y)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(4,8);
return 0;
}2.3 带超时的互斥量
带超时的互斥锁主要是在原来互斥锁的基础上增加一个超时等待的功能,这样就不用一直去获取互斥锁,另外如果在等待的时间内还没有获得锁资源,在超时后还可以继续处理其他的事情。
超时互斥锁比普通的互斥锁多了两个接口,分别是:try_lock_for和try_lock_until,这两个接口的功能是设置获取互斥锁的等待超时时间。使用方法如下:
std::timed_mutex mtx;
void fireworks (int pid) {
// waiting to get a lock: each thread prints "-" every 200ms:
while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
std::cout << "-";
}
// got a lock! - wait for 1s, then this thread prints "*"
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << pid <<"*\n";
mtx.unlock();
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(fireworks,i);
for (auto& th : threads) th.join();
return 0;
}上面的代码通过while循环不断获取超时锁,如果达到超时时间还没有获取到锁就输出一个-,如果获取到锁则休眠1000毫秒输出*和线程号。代码运行结果如下:
------------------------------------0*
----------------------------------------3*
-----------------------------------9*
------------------------------8*
-------------------------6*
--------------------1*
---------------4*
----------7*
-----5*
2*