linux线程同步的方法

下面是一个线程不安全的例子:

#include<stdio.h>
#include<pthread.h>int ticket_num=10000000;void *sell_ticket(void *arg) {while(ticket_num>0) {ticket_num--;}
}int main() {pthread_t t1,t2,t3;pthread_create(&t1, NULL, &sell_ticket, NULL);pthread_create(&t2, NULL, &sell_ticket, NULL);pthread_create(&t3, NULL, &sell_ticket, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);printf("ticket_num=%d\n", ticket_num);return 0;
}

运行结果如下:

# gcc no_lock_demo.c -o no_lock_demo.out -pthread
# ./no_lock_demo.out
ticket_num=-2

最后运行的结果不是固定的,有可能是0、-1,如果有这个ticket_num变量代表是库存的话,那么就会出现库存为负数的情况,所以需要引入线程同步来保证线程安全。

Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、自旋锁、信号量。

互斥锁

互斥锁本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,当互斥锁由某个线程持有后,这个互斥锁会锁上变成lock状态,此后只有该线程有权力打开该锁,其他想要获得该互斥锁的线程都会阻塞,直到互斥锁被解锁。

互斥锁的类型:

  • 普通锁(PTHREAD_MUTEX_NORMAL):互斥锁默认类型。当一个线程对一个普通锁加锁以后,其余请求该锁的线程将形成一个 等待队列,并在该锁解锁后按照优先级获得它,这种锁类型保证了资源分配的公平性。一个 线程如果对一个已经加锁的普通锁再次加锁,将引发死锁;对一个已经被其他线程加锁的普 通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期的后果。

  • 检错锁(PTHREAD_MUTEX_ERRORCHECK):一个线程如果对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLK;对一个已 经被其他线程加锁的检错锁解锁或者对一个已经解锁的检错锁再次解锁,则解锁操作返回 EPERM。

  • 嵌套锁(PTHREAD_MUTEX_RECURSIVE):该锁允许一个线程在释放锁之前多次对它加锁而不发生死锁;其他线程要获得这个锁,则当前锁的拥有者必须执行多次解锁操作;对一个已经被其他线程加锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次解锁,则解锁操作返回EPERM。

  • 默认锁(PTHREAD_MUTEX_ DEFAULT):一个线程如果对一个已经加锁的默认锁再次加锁,或者虽一个已经被其他线程加锁的默 认锁解锁,或者对一个解锁的默认锁解锁,将导致不可预期的后果;这种锁实现的时候可能 被映射成上述三种锁之一。

相关方法:


// 静态方式创建互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 动态方式创建互斥锁,其中参数mutexattr用于指定互斥锁的类型,具体类型见上面四种,如果为NULL,就是普通锁。
int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁,阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁,非阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁

例子:

#include<stdio.h>
#include<pthread.h>int ticket_num=10000000;pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;void *sell_ticket(void *arg) {while(ticket_num>0) {pthread_mutex_lock(&mutex);if(ticket_num>0) {ticket_num--;}pthread_mutex_unlock(&mutex);}
}int main() {pthread_t t1,t2,t3;pthread_create(&t1, NULL, &sell_ticket, NULL);pthread_create(&t2, NULL, &sell_ticket, NULL);pthread_create(&t3, NULL, &sell_ticket, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);printf("ticket_num=%d\n", ticket_num);return 0;
}

自旋锁

自旋锁顾名思义就是一个死循环,不停的轮询,当一个线程未获得自旋锁时,不会像互斥锁一样进入阻塞休眠状态,而是不停的轮询获取锁,如果自旋锁能够很快被释放,那么性能就会很高,如果自旋锁长时间不能够被释放,甚至里面还有大量的IO阻塞,就会导致其他获取锁的线程一直空轮询,导致CPU使用率达到100%,特别CPU时间。

相关方法:

int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 创建自旋锁int pthread_spin_lock(pthread_spinlock_t *lock); // 加锁,阻塞
int pthread_spin_trylock(pthread_spinlock_t *lock); // 尝试加锁,非阻塞
int pthread_spin_unlock(pthread_spinlock_t *lock); // 解锁

例子:

#include<stdio.h>
#include<pthread.h>int ticket_num=10000000;pthread_spinlock_t spinlock;void *sell_ticket(void *arg) {while(ticket_num>0) {pthread_spin_lock(&spinlock);if(ticket_num>0) {ticket_num--;}pthread_spin_unlock(&spinlock);}
}int main() {pthread_spin_init(&spinlock, 0);pthread_t t1,t2,t3;pthread_create(&t1, NULL, &sell_ticket, NULL);pthread_create(&t2, NULL, &sell_ticket, NULL);pthread_create(&t3, NULL, &sell_ticket, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);printf("ticket_num=%d\n", ticket_num);return 0;
}

信号量

信号量是一个计数器,用于控制访问有限共享资源的线程数。

相关方法:

// 创建信号量
// pshared:一般取0,表示调用进程的信号量。非0表示该信号量可以共享内存的方式,为多个进程所共享(Linux暂不支持)。
// value:信号量的初始值,可以并发访问的线程数。
int sem_init (sem_t* sem, int pshared, unsigned int value);int sem_wait (sem_t* sem); // 信号量减1,信号量为0时就会阻塞int sem_trywait (sem_t* sem); // 信号量减1,信号量为0时返回-1,不阻塞int sem_timedwait (sem_t* sem, const struct timespec* abs_timeout); // 信号量减1,信号量为0时阻塞,直到abs_timeout超时返回-1int sem_post (sem_t* sem); // 信号量加1

例子:

#include<stdio.h>
#include<pthread.h>
#include <semaphore.h>int ticket_num=10000000;sem_t sem;void *sell_ticket(void *arg) {while(ticket_num>0) {sem_wait(&sem);if(ticket_num>0) {ticket_num--;}sem_post(&sem);}
}int main() {sem_init(&sem, 0, 1); // value=1表示最多1个线程同时访问共享资源,与互斥量等价pthread_t t1,t2,t3;pthread_create(&t1, NULL, &sell_ticket, NULL);pthread_create(&t2, NULL, &sell_ticket, NULL);pthread_create(&t3, NULL, &sell_ticket, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);printf("ticket_num=%d\n", ticket_num);return 0;
}

条件变量

条件变量可以让调用线程在满足特定条件的情况下运行,不满足条件时阻塞等待被唤醒,必须与互斥锁搭配使用。

条件变量常用于生产者与消费者模型。

相关方法:

pthread_cond_t cond=PTHREAD_COND_INITIALIZER; // 创建条件变量,一个互斥锁可以对应多个条件变量int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex); // 阻塞等待条件满足,同时释放互斥锁mutexint pthread_cond_timedwait (pthread_cond_t* cond,pthread_mutex_t* mutex,const struct timespec* abstime); // 带超时的阻塞等待条件满足,同时释放互斥锁mutex// 从条件变量cond中唤出一个线程,令其重新获得原先的互斥锁
// 被唤出的线程此刻将从pthread_cond_wait函数中返回,但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
int pthread_cond_signal (pthread_cond_t* cond);// 从条件变量cond中唤出所有线程
int pthread_cond_broadcast (pthread_cond_t* cond);

例子:

#include<stdio.h>
#include<pthread.h>int max_buffer=10;
int count=0;pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t notempty=PTHREAD_COND_INITIALIZER;
pthread_cond_t notfull=PTHREAD_COND_INITIALIZER;void *produce(void *args) {while(1) {pthread_mutex_lock(&mutex);while(count == max_buffer) {printf("buffer is full, wait...\n");pthread_cond_wait(&notfull, &mutex);}printf("produce ...\n");count++;sleep(1);pthread_cond_signal(&notempty);pthread_mutex_unlock(&mutex);}}void *consumer(void *args) {while(1) {pthread_mutex_lock(&mutex);while(count == 0) {printf("buffer is empty, wait...\n");pthread_cond_wait(&notempty, &mutex);}printf("consumer ...\n");count--;sleep(1);pthread_cond_signal(&notfull);pthread_mutex_unlock(&mutex);}}int main() {pthread_t t1,t2,t3,t4;pthread_create(&t1, NULL, &produce, NULL);pthread_create(&t2, NULL, &produce, NULL);pthread_create(&t3, NULL, &consumer, NULL);pthread_create(&t4, NULL, &consumer, NULL);pthread_join(t1, NULL);return 0;
}

读写锁

读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的,当它以写模式锁住时,它是以独占模式锁住的,读读共享,读写互斥。

相关方法:

// 创建读写锁
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 加读锁,阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 加写锁,阻塞
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 释放读锁或者写锁int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // 尝试加读锁,非阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 尝试加写锁,非阻塞

例子:

#include <stdio.h>
#include <pthread.h>pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;void *read(void *arg) {while(1) {pthread_rwlock_rdlock(&rwlock);rintf("read message.\n");sleep(1);pthread_rwlock_unlock(&rwlock);sleep(1);}
}
void *write(void *arg) {while(1) {pthread_rwlock_wrlock(&rwlock);printf("write message.\n");sleep(1);pthread_rwlock_unlock(&rwlock);sleep(1);}
}int main(int argc,char *argv[]) {pthread_t t1,t2,t3;pthread_create(&t1, NULL, &read, NULL);pthread_create(&t2, NULL, &read, NULL);pthread_create(&t3, NULL, &write, NULL);pthread_join(t1, NULL);return 0;
}

屏障

屏障(barrier)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后所有线程都从该点继续执行。pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出。但屏障对象的概念更广,允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出,当所有的线程达到屏障后可以接着工作。

相关方法:

// 创建屏障
int pthread_barrier_init(pthread_barrier_t *barrier,const pthread_barrrierattr_t *attr,unsigned int count)// 阻塞等待,直到所有线程都到达
int pthread_barrier_wait(pthread_barrier_t *barrier)

例子:

#include <stdio.h>
#include <pthread.h>pthread_barrier_t barrier;void *go(void *arg){sleep (rand () % 10);printf("%lu is arrived.\n", pthread_self());pthread_barrier_wait(&barrier);printf("%lu go shopping...\n", pthread_self());
}int main() {pthread_barrier_init(&barrier, NULL, 3);pthread_t t1,t2,t3;pthread_create(&t1, NULL, &go, NULL);pthread_create(&t2, NULL, &go, NULL);pthread_create(&t3, NULL, &go, NULL);pthread_join(t1, NULL);return 0;
}

linux中实现线程同步的6种方法相关推荐

  1. linux:线程同步的5种方法

    linux:线程同步的5种方法 一.为什么要使用线程: 二.线程同步的5种方法 2.1 互斥量 2.2 读写锁 2.3 条件变量 2.4 自旋锁 2.5 屏障 一.为什么要使用线程: <1> ...

  2. Linux中的线程同步机制-futex

    Linux中的线程同步机制(一) -- Futex 引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这 ...

  3. C++中线程同步的四种方法(Win32平台)

    1.同步和互斥 互质是一种特殊的同步.线程同步一般指线程之间的执行存在某种程度上的相互依赖关系. 2.C++中线程同步的四种方法 (1)事件(Event); (2)信号量(semaphore); (3 ...

  4. linux如何创建共享内存,linux实现共享内存同步的四种方法

    https://blog.csdn.net/sunxiaopengsun/article/details/79869115 本文主要对实现共享内存同步的四种方法进行了介绍. 共享内存是一种最为高效的进 ...

  5. C#线程同步的几种方法

    在网上也看过一些关于线程同步的文章,其实线程同步有好几种方法,下面我就简单的做一下归纳. 一.volatile关键字 volatile是最简单的一种同步方法,当然简单是要付出代价的.它只能在变量一级做 ...

  6. 归纳一下:C#线程同步的几种方法

    我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态:或者你的程序需要访问一些外部资源如数据库或网络文件等.这些情况你都可以创建一个子线程 ...

  7. Linux中执行shell脚本的4种方法

    这篇文章主要介绍了Linux中执行shell脚本的4种方法总结,即在Linux中运行shell脚本的4种方法,需要的朋友可以参考下. bash shell 脚本的方法有多种,现在作个小结.假设我们编写 ...

  8. unix c线程同步的三种方法:互斥量、读写锁以及条件变-xhb8413-ChinaUnix博客

    unix c线程同步的三种方法:互斥量.读写锁以及条件变-xhb8413-ChinaUnix博客 unix c线程同步的三种方法:互斥量.读写锁以及条件变 2012-03-30 14:42:38 分类 ...

  9. Linux中执行shell脚本的5种方法总结

    Linux中执行shell脚本的4种方法总结,即在Linux中运行shell脚本的4种方法: 方法一:切换到shell脚本所在的目录(此时,称为工作目录)执行shell脚本: 复制代码 代码如下: c ...

最新文章

  1. 一则利用内核漏洞获取root权限的案例【转】
  2. 如何跨域取到response额外的的headers
  3. html %3ca id=%3e,a.markdown
  4. java线程——中断线程+线程状态+线程属性(优先级)
  5. exe msdt 无法上网_软网推荐:可装EXE程序的ReactOS
  6. Asp学习者完整攻略之三:操作SQL:SQL基础:
  7. jmeter插件下载
  8. 你的计算机无法启动一键还原,电脑一开机就进入dos之家的一键还原硬盘版,无法进入系统...
  9. 接口压力测试神器Jmeter
  10. 2020-2021 年度广东省职业院校学生专业技能大赛网络空间安全赛项竞赛规程
  11. ios支付宝客户端集成流程
  12. 怎么从视频中提取音频,这四个方法简单实用!
  13. pta中java编程题_多文件编程题
  14. 吉米小轻杆吸尘器轻巧便利顺手吸尘,利用碎片化时间让家居更洁净
  15. process-on在线绘制架构图,xmind绘制思维导图
  16. 一看就懂!任务提交的资源判断在Taier中的实践
  17. 苹果cms v10 仿韩剧tv
  18. 申宝股票-家居和家电板块大涨
  19. Cesium|xt3d加载中国地形
  20. Blast中文手册(3)

热门文章

  1. JS--对象数组深拷贝的方法
  2. AuthenticationManager 的 authentication 过程
  3. 网站图片优化有哪些?
  4. 微信小程序实现:月经记录与预测显示
  5. 钉钉自定义Outgoing机器人开发
  6. Python数据类型(三)数据结构类型—list、tuple、range、set、frozenset、dict
  7. 通俗易懂权限管理模块设计-Java
  8. c++ 关键字 mutable
  9. 详细了解AndroidStudio
  10. 英语中表示过道的几个词的区别