Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money

Today, instead of going over the GO language, we’re going to share some of the C code we wrote earlier, and look at a demo of mutex, spin locking, and atomic operations

The mutex

A critical section resource is occupied by one thread, and when another thread accesses the critical section resource, the CPU switches the thread, and does not allow the subsequent thread to run

It is suitable for locking a lot of content (such as adding nodes with red and black numbers), and the cost of thread switching is less than the cost of waiting

spinlocks

A critical section resource is already occupied by one thread, and when another thread comes to access the critical section resource, it is like a while(1).

Constantly check to see if the resource is available. If it is available, access the critical resource. If not, continue the loop

This applies to things that are less locked (such as ++ operations) and the cost of switching threads is greater than the cost of waiting

Atomic operation

The operations performed are completely indivisible and either all succeed or all fail

The best way to do that is to apply atomic manipulation

In field

Requirement scenarios:

Increment count 100000 times in 10 threads to see if the result is 10*100000

  • Create 10 threads in main
  • Call inc in thread function to add data
  • Use mutexes, spinlocks, and atomic operations to control, respectively

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#define PTHREAD_NUM	10
#define INFO	printf


pthread_mutex_t mutex;
pthread_spinlock_t spin;


int inc(int *v,int add)
{
	int old;
    // assembler, do an atomic operation
	__asm__ volatile(
		"lock; xaddl %2, %1;"
		:"=a" (old)
		:"m"(*v),"a"(add)
		:"cc"."memory"
	);
	
	return old;
}

void * thread_callback(void *arg)
{
	int *count = (int *)arg;

	int i = 100000;
	
while(i--)
	{
	#if 0
/ / the mutex
		pthread_mutex_lock(&mutex);
		(*count)++;
		pthread_mutex_unlock(&mutex);
	#elif 0
/ / the spin lock
		pthread_spin_lock(&spin);
		(*count)++;
		pthread_spin_unlock(&spin);
	#else
// Atomic operation
		inc(count,1);
	
	#endif
		usleep(1); }}int main(a)
{
	pthread_t thread[PTHREAD_NUM] = {0};
	pthread_mutex_init(&mutex,NULL);
	pthread_spin_init(&spin,0);
	
	int count  = 0;

	for(int i = 0; i<PTHREAD_NUM; i++){ pthread_create(&thread[i],NULL,thread_callback,&count);
	}

	for(int i = 0; i<100; i++) { INFO("count == %d\n",count);
		sleep(1);
	}
		
	
	return 0;
}

Copy the code

The code above is simple enough that XDM of interest can run on its own and control itself using mutexes, spinlocks or atomic operations to see how it compares

2. Performance comparison of Mutex, Lock and Atomic

We can use the following code to see how mutex, Lock, and Atomic perform

/ / concurrent
// mutex
// The CPU will be released if the resource is not available
// Usage scenarios
// The shared area executes a lot of content
// spinlock spinlock
// If the resource is not available, it spins in place, busy, etc
// Usage scenarios
// Less content is executed in the shared area
// Atomic operation
// Indivisible
// Usage scenarios
// Do simple ++, -- operations
//


#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

#define MAX_PTHREAD 2
#define LOOP_LEN    1000000000
#define LOOP_ADD    10000

int count = 0;

pthread_mutex_t mutex;
pthread_spinlock_t spin;

typedef void *(*functhread)(void *arg);

void do_add(int num)
{
	int sum = 0;
	for(int i = 0; i<num; i++) { sum +=i; }}int atomic_add(int *v,int add)
{
	int old;

	__asm__ volatile(
		"lock; xaddl %2, %1;"
		:"=a" (old)
		:"m"(*v),"a"(add)
		:"cc"."memory"
	);
	
	return old;
}

void * atomicthread(void *arg)
{

	for(int i  = 0; i<LOOP_LEN; i++){ atomic_add(&count,1); }}void * spinthread(void *arg)
{
	for(int i  = 0; i<LOOP_LEN; i++){ pthread_spin_lock(&spin); count++;//do_add(LOOP_ADD);pthread_spin_unlock(&spin); }}void * mutexthread(void *arg)
{
	for(int i  = 0; i<LOOP_LEN; i++){ pthread_mutex_lock(&mutex); count++;//do_add(LOOP_ADD);pthread_mutex_unlock(&mutex); }}int test_lock(functhread thre,void * arg)
{

	clock_t start = clock();
	pthread_t tid[MAX_PTHREAD] = {0};

	for(int i = 0; i<MAX_PTHREAD; i++) {// Create a thread
		int ret = pthread_create(&tid[i],NULL,thre,NULL);
		if(0! = ret) {printf("pthread create rror\n");
			return - 1; }}for(int i = 0; i<MAX_PTHREAD; i++){// Reclaim the thread
		pthread_join(tid[i],NULL);
	}

	clock_t end = clock();

	//printf("start -- %ld\n",start);
	//printf("end -- %ld\n",end);
	//printf("CLOCKS_PER_SEC -- %ld\n",CLOCKS_PER_SEC);
	printf("spec lock is -- %ld\n",(end - start)/CLOCKS_PER_SEC);

}


int main(a)
{
	pthread_mutex_init(&mutex,NULL);
	pthread_spin_init(&spin,0);
/ / spin test
	count = 0;
	printf("use spin ------ \n");
	test_lock(spinthread,NULL);
	printf("count == %d\n",count);


/ / test mutex
	count = 0;
	printf("use mutex ------ \n");
	test_lock(mutexthread,NULL);
	printf("count == %d\n",count);

/ / atomic test
	count = 0;
	printf("use automic ------ \n");
	test_lock(atomicthread,NULL);
	printf("count == %d\n",count);

	return 0;
}



Copy the code

The results of

From the above results, we can see that the mutex, the spin lock, and the atomic operation all add up correctly as I expected, but there is still some difference in time:

Spin-locks and mutex perform about the same here, but atomic operations are much faster

Welcome to like, follow and favorites

Friends, your support and encouragement, I insist on sharing, improve the quality of the power

All right, that’s it for this time

Technology is open, our mentality, should be more open. Embrace change, live in the sun, and strive to move forward.

I am Nezha, welcome to like, see you next time ~