700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Linux C/C++ 并发下的技术方案(互斥锁 自旋锁 原子操作)

Linux C/C++ 并发下的技术方案(互斥锁 自旋锁 原子操作)

时间:2021-01-09 06:55:07

相关推荐

Linux C/C++ 并发下的技术方案(互斥锁 自旋锁 原子操作)

前言

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程,但轻量进程更多指内核线程,而把用户线程称为线程。

一、为什么要使用锁?

下面是一个主线程count计数的方案,分别通过它的10个子线程进行count的自增。

mkdir LOCK//创建LOCK文件夹cd LOCK//转到LOCK目录下touch Lock.c//创建Lock.c文件gcc -o lock Lock.c -lpthread//编译的时候需要用pthread动态库

然后这里是Lock.c的代码

#include<stdio.h>#include<pthread.h>#define THREAD_COUNT 10void* thread_callback(void* arg) {int* pcount = (int*)arg;int i = 0;while (i++ < 1000000) {(*pcount)++;usleep(1);}}int main() {pthread_t threadid[THREAD_COUNT] = { 0 };int i = 0;int count = 0;for (i = 0; i < THREAD_COUNT; i++) {pthread_create(&threadid[i], NULL, thread_callback, &count);//创建线程(线程id,线程属性,线程入口函数,主线程往子线程传的参数)}for (i = 0; i < 100; i++) {printf("count: %d\n", count);sleep(1);}getchar();}

因为电脑配置不同,所以CPU的处理不同。

如果每个子线程使count自增10万次

最后count值可能等于100万(或者小于100w),

由于我的电脑每个子线程使count自增100万次,才会小于1000万,所以我以每个子线程自增100万次作为演示。

​在这里可以发现,明明count应该达到1000万才对,但是由于操作系统进程的切换导致count++这条语句出现了问题。

这是count++转化为汇编的语句,分为三条,在正常情况下,线程一执行完这三条语句再执行线程二.

​但是异常的情况下,线程一可能三条语句执行不完,就会进行线程二的执行。

导致的结果就是明明应该使count自增2次,但实践上只自增了1次,这样的结果就会导致1000万条数据有所衰减。

​所以为了避免这种情况的出现,我们可以使用,互斥锁、自旋锁、原子操作等方法解决这个问题。

C++后台开发系统学习地址:C/C++Linux服务器开发高级架构师/C++后台开发架构师​

以下学习资料,C++后台开发面试题,教学视频,C++后台开发学习路线图,免费分享有需要的可以自行添加:学习资料群79036 自取

二、解决方法(互斥锁、自旋锁、原子操作)

1.互斥锁

代码如下(示例):

#include<stdio.h>#include<pthread.h>#define THREAD_COUNT 10pthread_mutex_t mutex;//添加一个互斥锁void* thread_callback(void* arg) {int* pcount = (int*)arg;int i = 0;while (i++ < 1000000) {#if 0#elsepthread_mutex_lock(&mutex);//线程调用函数让互斥锁上锁(*pcount)++;pthread_mutex_unlock(&mutex);//解除互斥锁的锁定#endifusleep(1);}}int main() {pthread_t threadid[THREAD_COUNT] = { 0 };pthread_mutex_init(&mutex, NULL);//线程初始化int i = 0;int count = 0;for (i = 0; i < THREAD_COUNT; i++) {pthread_create(&threadid[i], NULL, thread_callback, &count);//创建线程(线程id,线程属性,线程入口函数,主线程往子线程传的参数)}for (i = 0; i < 100; i++) {printf("count: %d\n", count);sleep(1);}getchar();}

2.自旋锁

代码如下(示例):

#include<stdio.h>#include<pthread.h>#define THREAD_COUNT 10pthread_spinlock_t spinlock;//添加一个自旋锁void* thread_callback(void* arg) {int* pcount = (int*)arg;int i = 0;while (i++ < 1000000) {#if 0#elsepthread_spin_lock(&spinlock);//线程调用函数(*pcount)++;pthread_spin_unlock(&spinlock);#endifusleep(1);}}int main() {pthread_t threadid[THREAD_COUNT] = { 0 };pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED);int i = 0;int count = 0;for (i = 0; i < THREAD_COUNT; i++) {pthread_create(&threadid[i], NULL, thread_callback, &count);//创建线程(线程id,线程属性,线程入口函数,主线程往子线程传的参数)}for (i = 0; i < 100; i++) {printf("count: %d\n", count);sleep(1);}getchar();}

3.原子操作

代码如下(示例):

#include<stdio.h>#include<pthread.h>#define THREAD_COUNT 10int inc(int* value, int add) {int old;__asm__ volatile(//汇编语句"lock; xaddl %2,%1;"//使 %1的结果为%2+%1: "=a" (old): "m" (*value), "a"(add): "cc", "memory");return old;}void* thread_callback(void* arg) {int* pcount = (int*)arg;int i = 0;while (i++ < 1000000) {#if 0#elseinc(pcount, 1);#endifusleep(1);}}int main() {pthread_t threadid[THREAD_COUNT] = { 0 };int i = 0;int count = 0;for (i = 0; i < THREAD_COUNT; i++) {pthread_create(&threadid[i], NULL, thread_callback, &count);//创建线程(线程id,线程属性,线程入口函数,主线程往子线程传的参数)}for (i = 0; i < 100; i++) {printf("count: %d\n", count);sleep(1);}getchar();}

4.三种方法的比较(个人理解)

自旋锁:线程调用时相当于执行while(1)语句,直到获取锁内内容执行完,才进行下一个线程。

互斥锁:如果当前线程没有执行完,引起线程切换,就会执行下一条线程,当再次回来的时候重新执行。

原子操作:把多条语句合并成一条语句。

自旋锁适合锁的内容很少的时候使用,而互斥锁适合锁的内容较多的时候使用。

总结

今天通过这个例子,我让大家理解了,主线程和子线程之间可能会发生一些事情,导致程序最后不能达到我们预期的想法,因此就需要通过锁定语句的方法来使程序正常执行。

参考资料

C/C++ Linux高级开发课程推荐一个零声教育C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。