c++程序并发的各种实现
前言
这几天一直在面试,经常问到线程进程区别,怎么用,什么的。我虽然会教科书式回答,但在c++中没有用过,心里总是虚的。
于是下定决心,好好学习下多进程-及其进程通信方式、多线程-及其线程间通信方式,啃下这个骨头。
另外挺感谢4.11百度第一个面试官的,脑袋短路,手撕本能写出来的,太紧张了太紧张了。
最后虽然答的不好还是放我过了。。。哎 不知道什么时候能改掉自己有些特定场景紧张的坏毛病。
多进程
fork系统调用…改天再看
多线程
c++ 11 thread,在c++ 11以前,windows和linux下的多线程是不同的,具体哪些我没有看。
c++ 11后,使得多线程程序的可移植性大大提高。
创建一个线程并运行
1 | void thread_test(){ |
这里就会创建一个线程,这个线程去执行thread_test。
一创建thread对象,这个线程就跑起来了,thread_test是线程执行的行为。
join(),主线程阻塞等待子线程同步。
detach(),主线程和子线程分离,各干各事。
joinable(),返回线程对象是否可join,来自cppreference的解释:
A thread object is joinable if it represents a thread of execution.
A thread object is not joinable in any of these cases:
- if it was default-constructed.
- if it has been moved from (either constructing another thread object, or assigning to it).
- if either of its members join or detach has been called.
带参数函数传入thread对象
1 | thread sec(thread_test, param1, param2....); |
引用作为带参数函数的形参
对于引用,thread内部机制是默认拷贝构造到函数形参上的,这是为确保在使用detach时,主线程先结束了而传入的又是局部变量所导致的问题,这里我并没有做实验,不同编译器可能有出入。推测不用引用怕是拷贝2次。。
1 | void thread_test(const int &tmp){ |
线程sec里面的var并不是main里面的var。如果不想拷贝构造,你必须给我用引用呢?std::ref()即可,但在detach时切记小心处理。
智能指针作为带参数函数的形参
1 | void thread_test(unique_ptr<int> ptr){ |
是报错的,因为unique_ptr是独占的,不可拷贝构造。所以要么你std::move一下,要么用shared_ptr或者weak_ptr。
对象作为传入参数
两种方法:
- 重载()运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class test{
public:
int var;
test(int a):var(a){
cout << "构造函数" << endl;
};
test (const test &obj):var(obj.var){
cout << "拷贝构造函数" << endl;
}
void runable(){
cout << "test::runable() function... " << var << endl;
return;
}
void operator() (){
cout << "overload () to runable... " << var << endl;
return;
}
};
int main()
{
test A(10);
thread th2(A);
th2.join();
return 0;
}
运行现象:
1 | 构造函数 |
调用两次拷贝构造是因为gcc的问题吧。。。
- 传入对象成员函数运行现象:
1
2
3
4
5
6
7int main()
{
test myobj(10);
thread th1(test::runable, &myobj, 10);
th1.join();
return 0;
}1
2构造函数
test::runable() function... 10
上面两种方法,一个传的对象引用,一个传的对象值。这样带来一个问题,如果两个线程都需要对同一对象进行操作,并且线程间有共享数据,那么得使用引用。
互斥量
如果两个线程对共享数据进行写操作,需要引入互斥量,否则并不能得到正确结果。
对需要加锁的代码执行lock()成员函数,与trylock()的区别是是否阻塞,互斥量本质上也就只是一个“变量”而已,可以自己实现一个简陋版本的,但肯定不会是轮询的。。。
1 | #include <iostream> |
如果lock之后忘记unlock,可以引入std::lock_guard模板取代lock和unlock,第二个参数std::adopt_lock参数,表示不需要再锁了(此前锁过)。
1 | std::lock_guard<std::mutex> newguard(mymutex); |
方便,大概相当于构造时锁住,析构时解锁。
或者unique_lock模板,第二个参数,std::adpot_lock,std::try_to_lock,std::defer_lock,try_to_lock不阻塞,defer_lock不锁。成员函数:lock, unlock, own_lock, release…等等,可移动不可复制,独占。
1 | std::unique_lock<std::mutex> newguard(mymutex,std::defer_lock); |
死锁
两把锁头,线程A获得了互斥锁1,尝试获得互斥锁2,线程B获得了互斥锁2,尝试获得互斥锁1,lock()则会一直阻塞,产生死锁。
1 | A: |
解决方法:
- 都先锁1再锁2
- std::lock()模板
线程间通信方式
- 线程共享进程的堆
- 线程独有栈
c++程序并发的各种实现