C++ multithreading learning (eleven, atomic operations and atomic types)

Table of contents

atomic operation

Simply write a program:

atomic creates atomic variables

The reason why the result of value2 is not 2w:

atomic atomic type

First simply write 3 operations:

introduce:

1. The specialized type of atomic

2. memory_order internal enumeration

1. memory_order_relaxed:

2. memory_order_consume:

3. memory_order_acquire:

4. memory_order_release:

5. memory_order_acq_rel:

6. memory_order_seq_cst:

Why are there these orders:

strong order

weak order

Small case:


atomic operation

Atomic operation: Read and write operations on variables are indivisible.

Simply write a program:

#include <iostream>
#include <thread>
using namespace std;
int value = 0;
void testFunc()
{
	for (int i=0;i<10000;i++)
	{
		value++;
	}
}
int main()
{
	thread t1(testFunc);
	thread t2(testFunc);
	t1.join();
	t2.join();
	cout << value;
	return 0;
}

There are two threads (t1 and t2) in this code, and they increment the same global variable value at the same time.

Since these two threads are executed in parallel, they may read the current value of value at the same time, and then perform the self-increment operation at the same time, and write back the new value. When a thread reads value, the value of value may have been Another thread increments itself. In this case, the self-increment operation of the thread will cause a race condition, resulting in unpredictable final results.

In this code, if two threads read the initial value of value 0 at the same time, and then perform 10,000 self-increment operations at the same time, the final result of value will be 20,000 [if the computer is good, this situation is more common ] .

The result on some people's computers may be more than 10,000 random values, which is caused by the uncertain scheduling and execution order of threads. Different operating systems, hardware, and other factors may affect the scheduling and execution order of threads, resulting in different results.

atomic creates atomic variables

Atomic variables can be created by atomic<int> atm_Value=0 and atomic<int> atm_Value(0) .

It is worth noting that the copy construction and assignment operations in atomic have been deleted.

#include <iostream>
#include <thread>
#include <atomic>//模板类 创建原子变量
using namespace std;
int value = 0;
atomic<int> atm_Value(0);
atomic<int> atm_Value2(0);
void testFunc()
{
	for (int i=0;i<10000;i++)
	{
		value++;
		atm_Value++;//如果有一个线程对他进行操作,则另一个线程需要等他做完操作之后,才能进行操作,这个是及其稳定的
		atm_Value2 = atm_Value2 + 1;//一般情况下,分为两步的操作是不满足原子操作的
	}
}
int main()
{
	thread t1(testFunc);
	thread t2(testFunc);
	t1.join();
	t2.join();
	cout << value<<endl;
	cout << atm_Value << endl;
	cout << atm_Value2 << endl;
	return 0;
}

The reason why the result of value2 is not 2w:

In a multi-threaded environment, if multiple threads perform +1 operations on atm_Value2 at the same time , a race condition will occur, that is, multiple threads read the value of atm_Value2 at the same time and try to perform an addition operation, and then assign the result to atm_Value2 .

Since there are read-modify-write operations in this process, if multiple threads execute at the same time, data inconsistency may result. 

atomic atomic type

First simply write 3 operations:

    atomic<int> atm_Num1(2);
	atomic_int atm_Num2(2);
	//原子类型操作
	//写操作
	atm_Num1.store(2, memory_order::memory_order_relaxed);
	//读操作
	atm_Num1.load(memory_order::memory_order_relaxed);
	//修改
	atm_Num1.exchange(2, memory_order::memory_order_relaxed);

In the code, the author uses two atomic types: atomic and atomic_int .

atomic is a general atomic type that can be used for any copyable data type;

And atomic_int is a specialized version of atomic , specially for int type.

It shows several common atomic type operations:
1. The store function is used to store a value into an atomic object, and specifies the memory order.
2. The load function is used to read the value from the atomic object, and the memory order is specified.
3. The exchange function is used to replace the value of the atomic object with a new value and return the original value.

In this code, the author uses memory_order_relaxed as the memory order parameter, which means that there is no need to pay attention to the order of memory access, so what else is in the specific memory_order ?

In actual use, the appropriate memory order parameters will be selected according to specific needs.

introduce:

1. Specialized types of atomic

After knowing that atomic has specialized versions for different types, we can enter the source code to see how many specialized types there are:

using atomic_bool = atomic<bool>;

using atomic_char   = atomic<char>;
using atomic_schar  = atomic<signed char>;
using atomic_uchar  = atomic<unsigned char>;
using atomic_short  = atomic<short>;
using atomic_ushort = atomic<unsigned short>;
using atomic_int    = atomic<int>;
using atomic_uint   = atomic<unsigned int>;
using atomic_long   = atomic<long>;
using atomic_ulong  = atomic<unsigned long>;
using atomic_llong  = atomic<long long>;
using atomic_ullong = atomic<unsigned long long>;

2. memory_order internal enumeration

enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
};

1. memory_order_relaxed:

Loose ordering, which allows the compiler and hardware to optimize atomic operations without guaranteeing any order relationship or visibility.

2. memory_order_consume:

The consumption order ensures that the current thread will not read the atomic operation earlier than other threads load a dependent value of the atomic operation.

3. memory_order_acquire:

The acquisition order guarantees that the current thread's reads of the atomic operation will not be loaded earlier than subsequent reads of the atomic operation by other threads.

4. memory_order_release:

The release order ensures that the current thread's write operation to the atomic operation will not be earlier than the previous continuation write operation of the atomic operation stored by other threads.

5. memory_order_acq_rel:

Acquire-release order, with both memory_order_acquire and memory_order_release features.

6. memory_order_seq_cst:

Sequential consistency, the strictest memory order, ensures that all threads read and write the atomic operation in the global order.

Why are there these orders:

For example, I write a function:

atomic<int> a = 0;
atomic<int> b = 0;
void test()
{
	int t = 1;
	a = t;
	b = 1;
}

Atomic operations in multithreading can guarantee the atomicity of each operation, but the order of multiple atomic operations cannot be guaranteed .

So in this case, ` b = 1` may execute faster than other codes.

strong order

The code order is the same as the execution order

weak order

The execution order of the code will be adjusted appropriately by the processor and will not affect the normal execution of the program

Small case:

In fact, the functions in atomic are overloaded , so in fact, it is not necessary to write the related  memory_order:: enumeration.

Just fill in the value directly, for example: a.store (t);

#include<iostream>
#include<thread>
#include <atomic>
using namespace std;

atomic<int> a = 0;
atomic<int> b = 0;
void setValue()
{
	int t = 1;
	a.store(t,memory_order::memory_order_release);//相当于a=t
	b.store(2, memory_order::memory_order_relaxed);
}
void print()
{
	//cout << a << " : " << b << endl;//这样子输出不一定是1:2
	cout << a.load(memory_order::memory_order_relaxed)<<"    " << b.load(memory_order::memory_order_relaxed) << endl;//这样子输出也不一定是1:2
	b.exchange(9999, memory_order::memory_order_relaxed);
}
int main()
{
	thread t1(setValue);
	thread t2(print);
	t1.join();
	t2.join();

	cout << b.load(memory_order::memory_order_relaxed);
	return 0;
}

Guess you like

Origin blog.csdn.net/q244645787/article/details/131611257