【Linux 线程】线程同步《三》
1、条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥
(1)创建和注销
条件变量和互斥锁一样,都有静态&动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态方式调用pthread_cond_init()函数,API定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:
int pthread_cond_destroy(pthread_cond_t *cond)
(2)等待和激发
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
(3)互斥变量举例:生产者&消费者
(4)举例:
《举例1》
/*************************************************************************
> File Name: pthread_cond1.c
> Summary: 条件变量应用---生产者&消费者 version1 单个生产者&单个消费者情形
> Author: xuelisheng
> Created Time: 2018年12月18日
************************************************************************/ #include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <pthread.h> // 链表作为共享数据,需要被互斥量保护
struct msg{
struct msg *next;
int num;
}; struct msg *head; // 静态初始化,一个条件变量和一个互斥量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *consumer(void *p)
{
struct msg *mp;
for( ; ; )
{
pthread_mutex_lock(&mutex);
while(head == NULL) // 说明此时没有节点,这里当只有一个消费者时,使用while和if都可以。如果有多个消费者则必须使用while
{
// 一开始 阻塞等待,并解锁mutex
// 收到signal信号之后,解除阻塞,加锁mutex
pthread_cond_wait(&has_product, &mutex);
}
mp = head;
head = mp->next; // 消费掉一个产品
pthread_mutex_unlock(&mutex); printf("Consume %lu --- %d\n", pthread_self(), mp->num);
free(mp);
sleep(rand()%); // 休眠:为了打印效果明显
}
} void *producer(void *p)
{
struct msg *mp;
for(; ;)
{
mp = malloc(sizeof(struct msg));
mp->num = rand() % + ; // 模拟生产一个产品(1-1000之间的一个数字)
printf("Produce ---------------- %d\n", mp->num); // 加互斥锁:mp为共享数据
pthread_mutex_lock(&mutex);
mp->next = head;
head = mp;
pthread_mutex_unlock(&mutex); pthread_cond_signal(&has_product); // 将等待在该条件变量上的一个线程唤醒,通知阻塞在条件变量上的线程
sleep(rand()%); // 休眠:为了打印效果明显
}
} int main()
{
pthread_t pid, cid;
srand(time(NULL)); // 创建线程
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&pid, NULL, consumer, NULL); // 等待回收
pthread_join(pid, NULL);
pthread_join(cid, NULL); return ;
}
运行结果(截取部分):
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Produce ----------------
Consume ---
Produce ----------------
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Consume ---
Produce ----------------
Consume ---
Consume ---
Produce ----------------
Produce ----------------
Produce ----------------
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Consume ---
Produce ----------------
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Consume ---
Produce ----------------
Consume ---
《举例2》
/*************************************************************************
> File Name: pthread_cond2.c
> Summary: 条件变量应用---生产者&消费者 version2 单个生产者&多个消费者情形
> Author: xuelisheng
> Created Time: 2018年12月18日
************************************************************************/ #include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <pthread.h> // 链表作为共享数据,需要被互斥量保护
struct msg{
struct msg *next;
int num;
}; struct msg *head; // 静态初始化,一个条件变量和一个互斥量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *consumer(void *p)
{
struct msg *mp;
for( ; ; )
{
pthread_mutex_lock(&mutex);
while(head == NULL) // 说明此时没有节点,这里当只有一个消费者时,使用while和if都可以。如果有多个消费者则必须使用while
{
// 一开始 阻塞等待,并解锁mutex
// 收到signal信号之后,解除阻塞,加锁mutex
pthread_cond_wait(&has_product, &mutex); // 多个消费者线程都阻塞在这里
}
mp = head;
head = mp->next; // 消费掉一个产品
pthread_mutex_unlock(&mutex); printf("Consume %lu --- %d\n", pthread_self(), mp->num);
free(mp);
sleep(rand()%); // 休眠:为了打印效果明显
}
} void *producer(void *p)
{
struct msg *mp;
for(; ;)
{
mp = malloc(sizeof(struct msg));
mp->num = rand() % + ; // 模拟生产一个产品(1-1000之间的一个数字)
printf("Produce ---------------- %d\n", mp->num); // 加互斥锁:mp为共享数据
pthread_mutex_lock(&mutex);
mp->next = head;
head = mp;
pthread_mutex_unlock(&mutex); pthread_cond_signal(&has_product); // 将等待在该条件变量上的一个线程唤醒,通知阻塞在条件变量上的线程
sleep(rand()%); // 休眠:为了打印效果明显
}
} int main()
{
pthread_t pid, cid;
srand(time(NULL)); // 创建线程
pthread_create(&pid, NULL, producer, NULL); // 创建多个消费者
pthread_create(&pid, NULL, consumer, NULL);
pthread_create(&pid, NULL, consumer, NULL);
pthread_create(&pid, NULL, consumer, NULL);
pthread_create(&pid, NULL, consumer, NULL); // 等待回收
pthread_join(pid, NULL);
pthread_join(cid, NULL); return ;
}
运行结果(截取部分):发现消费者线程id不同,即多个消费者
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Consume ---
Produce ----------------
Produce ----------------
Consume ---
Consume ---
Produce ----------------
Consume ---
【Linux 线程】线程同步《三》的更多相关文章
- linux 线程的同步 三 (内存信号量的使用)
信号量.同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已.但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通 ...
- Linux 多线程 - 线程异步与同步机制
Linux 多线程 - 线程异步与同步机制 I. 同步机制 线程间的同步机制主要包括三个: 互斥锁:以排他的方式,防止共享资源被并发访问:互斥锁为二元变量, 状态为0-开锁.1-上锁;开锁必须由上锁的 ...
- linux线程间同步方式总结梳理
线程间一般无需特别的手段进行通信,由于线程间能够共享数据结构,也就是一个全局变量能够被两个线程同时使用.只是要注意的是线程间须要做好同步! 使用多线程的理由: 1. 一个是和进程相比,它是一种非常&q ...
- Linux线程间同步的几种方式
信号量 信号量强调的是线程(或进程)间的同步:"信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞 ...
- Linux/Unix 线程同步技术之互斥量(1)
众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...
- Linux的线程同步对象:互斥量Mutex,读写锁,条件变量
进程是Linux资源分配的对象,Linux会为进程分配虚拟内存(4G)和文件句柄等 资源,是一个静态的概念.线程是CPU调度的对象,是一个动态的概念.一个进程之中至少包含有一个或者多个线程.这 ...
- Linux系统编程(29)——线程间同步(续篇)
线程间的同步还有这样一种情况:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行.在pthread库中通过条件变 ...
- linux线程间同步方式汇总
抽空做了下linux所有线程间同步方式的汇总(原生的),包含以下几个: 1, mutex 2, condition variable 3, reader-writer lock 4, spin loc ...
- java并发编程(三)----线程的同步
在现实开发中,我们或多或少的都经历过这样的情景:某一个变量被多个用户并发式的访问并修改,如何保证该变量在并发过程中对每一个用户的正确性呢?今天我们来聊聊线程同步的概念. 一般来说,程序并行化是为了获得 ...
- 深入Java线程管理(三):线程同步
一. 引入同步: 有一个很经典的案例,即银行取款问题.我们可以先看下银行取款的基本流程: 1)用户输入账户.密码,系统判断用户的账户.密码是否匹配. 2)用户输入取款金额. 3)系统判断账户金额是否大 ...
随机推荐
- How to start a VirtualBox VM headless in Windows 10
If you wanted to start a VirtualBox VM headless (no UI) in the past, you needed additional tools. I ...
- ABAP-串口通信-道闸设备
最近SAP系统需要与道闸设备集成,通过串口通讯模式控制道闸栏杆升降,在此将开发过程中的思路及问题点做个备注. 一.相关设备 道闸设备型号:富士智能FJC-D618 串口模块:康耐德 C2000-A1- ...
- Delphi TMemoryStream写入到字符串和字符串写入到流
一.TMemoryStream数据写入到字符串里 var lvStream:TMemoryStream; s:AnsiString; p: PAnsiChar; begin lvStream:= TM ...
- Delphi 修改本地日期和时间
procedure TForm1.ModifySysdate(D: Double); var systemtime:Tsystemtime; DateTime:TDateTime; begin Set ...
- django403错误(转)
原文:http://blog.sina.com.cn/s/blog_60ccc6e101011ku0.html 处理过程 1.按提示及google结果修改setting.py,在MIDDLEWARE_ ...
- 尚硅谷springboot学习19-日志切换
查看相关依赖关系,排除相关依赖,引入新的日志依赖 slf4j+log4j的方式: <dependency> <groupId>org.springframework.boot& ...
- NAT与FULL NAT的区别
LVS 当前应用主要采用 DR 和 NAT 模式,但这 2 种模式要求 RealServer 和 LVS在同一个 vlan中,导致部署成本过高:TUNNEL 模式虽然可以跨 vlan,但RealSer ...
- PHP使用redis防止大并发下二次写入(如如何防止重复下订单)
php调用redis进去读写操作,大并发下会出现:读取key1,没有内容则写入内容,但是大并发下会出现同时多个php进程写入的情况,这个时候需要加一个锁,即获取锁的php进程有权限写. $lock_k ...
- 计算机网络协议包头赏析-TCP
仍然先把TCP报文段的格式放在这里,然后我们看图说话: TCP报文段也分为首部和数据两部分,首部默认情况下一般是20字节长度,但在一些需求情况下,会使用“可选字段”,这时,首部长度会有所增加. 下面, ...
- jgGrid
jqGrid的表格加载 function GetGrid() { var selectedRowIndex = 0; var $gridTable = $("#gridTable" ...