c++程序并发的各种实现

前言

这几天一直在面试,经常问到线程进程区别,怎么用,什么的。我虽然会教科书式回答,但在c++中没有用过,心里总是虚的。

于是下定决心,好好学习下多进程-及其进程通信方式、多线程-及其线程间通信方式,啃下这个骨头。

另外挺感谢4.11百度第一个面试官的,脑袋短路,手撕本能写出来的,太紧张了太紧张了。
最后虽然答的不好还是放我过了。。。哎 不知道什么时候能改掉自己有些特定场景紧张的坏毛病。

多进程

fork系统调用…改天再看

多线程

c++ 11 thread,在c++ 11以前,windows和linux下的多线程是不同的,具体哪些我没有看。

c++ 11后,使得多线程程序的可移植性大大提高。

创建一个线程并运行

1
2
3
4
5
6
7
void thread_test(){
cout << "The test" << endl;
return;
}
thread first(thread_test);
first.join();
//first.detach;

这里就会创建一个线程,这个线程去执行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:

  1. if it was default-constructed.
  2. if it has been moved from (either constructing another thread object, or assigning to it).
  3. if either of its members join or detach has been called.

带参数函数传入thread对象

1
thread sec(thread_test, param1, param2....);

引用作为带参数函数的形参

对于引用,thread内部机制是默认拷贝构造到函数形参上的,这是为确保在使用detach时,主线程先结束了而传入的又是局部变量所导致的问题,这里我并没有做实验,不同编译器可能有出入。推测不用引用怕是拷贝2次。。

1
2
3
4
5
6
7
8
9
10
11
12
13
void thread_test(const int &tmp){
cout << "The test " << tmp << " address " << &tmp << endl;
return;
}

int main()
{
int var = 10;
cout << &var << endl;
thread sec(thread_test, var);
sec.join();
return 0;
}

线程sec里面的var并不是main里面的var。如果不想拷贝构造,你必须给我用引用呢?std::ref()即可,但在detach时切记小心处理。

智能指针作为带参数函数的形参

1
2
3
4
5
6
7
8
9
10
11
12
void thread_test(unique_ptr<int> ptr){
cout << "The test " << *ptr << endl;
return;
}

int main()
{
unique_ptr<int> ptr(new int(100));
thread th1(thread_test, ptr);
th1.join();
return 0;
}

是报错的,因为unique_ptr是独占的,不可拷贝构造。所以要么你std::move一下,要么用shared_ptr或者weak_ptr。

对象作为传入参数

两种方法:

  1. 重载()运算符
    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
    26
    class 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
2
3
4
构造函数
拷贝构造函数
拷贝构造函数
overload () to runable... 10

调用两次拷贝构造是因为gcc的问题吧。。。

  1. 传入对象成员函数
    1
    2
    3
    4
    5
    6
    7
    int main()
    {
    test myobj(10);
    thread th1(test::runable, &myobj, 10);
    th1.join();
    return 0;
    }
    运行现象:
    1
    2
    构造函数
    test::runable() function... 10

上面两种方法,一个传的对象引用,一个传的对象值。这样带来一个问题,如果两个线程都需要对同一对象进行操作,并且线程间有共享数据,那么得使用引用。

互斥量

如果两个线程对共享数据进行写操作,需要引入互斥量,否则并不能得到正确结果。
对需要加锁的代码执行lock()成员函数,与trylock()的区别是是否阻塞,互斥量本质上也就只是一个“变量”而已,可以自己实现一个简陋版本的,但肯定不会是轮询的。。。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <list>
#include <thread>
#include <time.h>
#include <mutex>
#include <unistd.h>
using namespace std;

class test
{
public:
void read()
{
for(int j=1;j<100;j++){
sleep(1);
while(1){
if(islock){
;
}
else{
break;
}
}
islock = true;
if (!var.empty()) {
cout << var.front() << endl;
var.pop_front();
}
islock = false;
}
}
void write()
{
for(int i=1;i<100;i++){
srand(time(NULL));
iSecret = rand() % 10 + 1;
cout << "ise: " << iSecret << " size: " << var.size() << endl;
while(1){
if(islock){
;
}
else{
break;
}
}
islock = true;
var.push_back(iSecret);
islock = false;
sleep(1);
}
}
private:
list<int> var;
mutex mymutex;
bool islock = false;
int iSecret;
};

int main()
{
//shared_ptr<int> ptr(new int(100));
test myobj;
thread th1(&test::write, &myobj);
thread th2(&test::read, &myobj);
th1.join();
th2.join();
return 0;
}

如果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
2
3
std::unique_lock<std::mutex> newguard(mymutex,std::defer_lock);
std::unique_lock<std::mutex> newguard(mymutex,std::adopt_lock);
std::unique_lock<std::mutex> newguard(mymutex,std::try_to_lock);

死锁

两把锁头,线程A获得了互斥锁1,尝试获得互斥锁2,线程B获得了互斥锁2,尝试获得互斥锁1,lock()则会一直阻塞,产生死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
A: 
mymutex1.lock();
mymutex2.lock();
if (!var.empty()) {
cout << var.front() << endl;
var.pop_front();
}
mymutex2.unlock();
mymutex1.unlock();
B:
mymutex2.lock();
mymutex1.lock();
var.push_back(iSecret);
mymutex1.unlock();
mymutex2.unlock();

解决方法:

  1. 都先锁1再锁2
  2. std::lock()模板

线程间通信方式

  1. 线程共享进程的堆
  2. 线程独有栈

c++程序并发的各种实现

https://lyq.blogd.club/2020/04/12/multi/

Author

lyq1996

Posted on

2020-04-12

Updated on

2023-01-05

Licensed under

Comments