linux c线程间通信之条件变量

条件变量是和互斥量一起使用的。
以下演示了2个线程,其中一个线程把一个变量修改为1,然后等待另外一个线程把这个变量修改为0,然后再重复以上动作。

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <semaphore.h>

/**
 *  https://linux.die.net/man/3/pthread_cond_wait
 *
 */

static pthread_mutex_t mutex;

static int key = 0;

// Declaration of thread condition variable
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;

void* calls(void *ptr) {
	int flag = 0;
	pthread_mutex_lock(&mutex);
	if (key == 1) {
		key = 0;
		pthread_cond_broadcast(&cond1);
	}

	while (1) {
		pthread_cond_wait(&cond1, &mutex);
		printf("1 after wait,key is : %d\n", key);
		if (key == 1) {
			key = 0;
			pthread_cond_broadcast(&cond1);
		}
	}
	return NULL;
}

void* calls2(void *ptr) {
	int flag = 0;
	pthread_mutex_lock(&mutex);

	if (key == 0) {
		key = 1;
		pthread_cond_broadcast(&cond1);
	}

	while (1) {
		pthread_cond_wait(&cond1, &mutex);
		printf("2 after wait,key is : %d\n", key);
		if (key == 0) {
			key = 1;
			pthread_cond_broadcast(&cond1);
		}
	}

	printf("thread2: exit \n");
	return NULL;
}

int main() {
	pthread_mutex_init(&mutex, NULL);
	pthread_t thread;
	pthread_t thread2;
	pthread_create(&thread, NULL, calls, NULL);
	sleep(1);
	pthread_create(&thread2, NULL, calls2, NULL);
	sleep(1);
	pthread_join(thread, NULL);
	pthread_join(thread2, NULL);
	if (0 != pthread_mutex_destroy(&mutex)) {
		perror("pthread_mutex_destroy");
	}
	if (pthread_cond_destroy(&cond1) != 0) {
		perror("pthread_cond_destroy() error");
		exit(2);
	}
	printf("main exit\n");
	return EXIT_SUCCESS;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
同步概念 所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致。等等 而,编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。 线程同步 同步即协同步调,按预定的先后次序运行。 线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。 举例1: 银行存款 5000。柜台,折:取3000;提款机,卡:取 3000。剩余:2000 举例2: 内存中100字节,线程T1欲填入全1, 线程T2欲填入全0。但如果T1执行了50个字节失去cpu,T2执行,会将T1写过的内容覆盖。当T1再次获得cpu继续 从失去cpu的位置向后写入1,当执行结束,内存中的100字节,既不是全1,也不是全0。 产生的现象叫做“与时间有关的错误”(time related)。为了避免这种数据混乱,线程需要同步。 “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。 因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。 数据混乱原因: 1. 资源共享(独享资源则不会) 2. 调度随机(意味着数据访问会出现竞争) 3. 线程间缺乏必要的同步机制。 以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。 所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。 互斥量mutex Linux中提供一把互斥锁mutex(也称之为互斥量)。 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 资源还是共享的,线程间也还是竞争的, 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。 但,应注意:同一时刻,只能有一个线程持有该锁。 当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。 所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。 因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。 主要应用函数: pthread_mutex_init函数 pthread_mutex_destroy函数 pthread_mutex_lock函数 pthread_mutex_trylock函数 pthread_mutex_unlock函数 以上5个函数的返回值都是:成功返回0, 失败返回错误号。 pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。 pthread_mutex_t mutex; 变量mutex只有两种取值1、0。 pthread_mutex_init函数 初始化一个互斥锁(互斥量) ---> 初值可看作1 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参1:传出参数,调用时应传 &mutex restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改 参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性 1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER; 2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL) pthread_mutex_destroy函数 销毁一个互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); pthread_mutex_lock函数 加锁。可理解为将mutex--(或-1) int pthread_mutex_lock(pthread_mutex_t *mutex); pthread_mutex_unlock函数 解锁。可理解为将mutex ++(或+1) int pthread_mutex_unlock(pthread_mutex_t *mutex); pthread_mutex_trylock函数 尝试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex); 加锁与解锁 lock与unlock: lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。 unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。 例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。 可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++ lock与trylock: lock加锁失败会阻塞,等待锁释放。 trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。 加锁步骤测试: 看如下程序:该程序是非常典型的,由于共享、竞争而没有加任何同步机制,导致产生于时间有关的错误,造成数据混乱: #include #include #include void *tfn(void *arg) { srand(time(NULL)); while (1) { printf("hello "); sleep(rand() % 3); /*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/ printf("world\n"); sleep(rand() % 3); } return NULL; } int main(void) { pthread_t tid; srand(time(NULL)); pthread_create(&tid, NULL, tfn, NULL); while (1) { printf("HELLO "); sleep(rand() % 3); printf("WORLD\n"); sleep(rand() % 3); } pthread_join(tid, NULL); return 0; } 【mutex.c】 【练习】:修改该程序,使用mutex互斥锁进行同步。 1. 定义全局互斥量,初始化init(&m, NULL)互斥量,添加对应的destry 2. 两个线程while中,两次printf前后,分别加lock和unlock 3. 将unlock挪至第二个sleep后,发现交替现象很难出现。 线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠。睡醒解锁后又立即加锁,这两个库函数本身不会阻塞。 所以在这两行代码之间失去cpu的概率很小。因此,另外一个线程很难得到加锁的机会。 4. main 中加flag = 5 将flg在while中-- 这时,主线程输出5次后试图销毁锁,但子线程未将锁释放,无法完成。 5. main 中加pthread_cancel()将子线程取消。 【pthrd_mutex.c】 结论: 在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。 死锁 1. 线程试图对同一个互斥量A加锁两次。 2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁 【作业】:编写程序,实现上述两种死锁现象。 读写锁 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。 读写锁状态: 一把读写锁具备三种状态: 1. 读模式下加锁状态 (读锁) 2. 写模式下加锁状态 (写锁) 3. 不加锁状态 读写锁特性: 1. 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。 2. 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。 3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。 读写锁非常适合于对数据结构读的次数远大于写的情况。 主要应用函数: pthread_rwlock_init函数 pthread_rwlock_destroy函数 pthread_rwlock_rdlock函数 pthread_rwlock_wrlock函数 pthread_rwlock_tryrdlock函数 pthread_rwlock_trywrlock函数 pthread_rwlock_unlock函数 以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。 pthread_rwlock_t类型 用于定义一个读写锁变量。 pthread_rwlock_t rwlock; pthread_rwlock_init函数 初始化一把读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 参2:attr表读写锁属性,通常使用默认属性,传NULL即可。 pthread_rwlock_destroy函数 销毁一把读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); pthread_rwlock_rdlock函数 以读方式请求读写锁。(常简称为:请求读锁) int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); pthread_rwlock_wrlock函数 以写方式请求读写锁。(常简称为:请求写锁) int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); pthread_rwlock_unlock函数 解锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); pthread_rwlock_tryrdlock函数 非阻塞以读方式请求读写锁(非阻塞请求读锁) int pthread_
第1篇 LinuxC语言基础 第1章 Linux简介 1.1 GNU简介 1.2 Linux简介 1.2.1 Linux发展史 1.2.2 Linux发行版 1.2.3 Linux内核版本 1.2.4 Linux与UNIX的关系 1.2.5 Linux在服务器方面的发展 1.2.6 Linux在嵌入式系统方面的发展 1.2.7 Linux在桌面系统方面的发展 1.3 Linux环境下的其他编程语言 1.3.1 C++ 1.3.2 Java 1.3.3 Perl 1.3.4 Python 1.3.5 Ruby 1.3.6 PHP 第2章 控制结构 2.1 goto语句 2.1.1 C语言中的无条件跳转 2.1.2 使用goto语句进行出错处理 2.1.3 出错处理的一般模型 2.2 C语言中的分支结构 2.2.1 分支结构的翻译 2.2.2 使用goto语句实现分支结构 2.3 短路计算 2.3.1 短路计算 2.3.2 &&运算的短路计算 2.3.3 ||运算的短路计算 2.4 C语言中的循环结构 2.4.1 while循环 2.4.2 do…while循环 2.4.3 for循环 2.5 switch语句 2.5.1 switch语句的应用 2.5.2 使用goto语句实现switch语句 2.6 优化控制结构 2.6.1 表达式优化——使用替换程序中的乘除法 2.6.2  表达式优化——常量折叠 2.6.3 表达式优化——使用数学公式 2.6.4 表达式优化——存储问题 2.6.5 分支优化——改变判断顺序 2.6.6 分支优化——使用switch语句 2.6.7 循环优化——一次性计算 第3章 C语言中的函数 3.1 函数的本质 3.2 变量的作用域和生命期 3.2.1 全局变量 3.2.2 局部变量 3.3 变量的初始值 3.3.1 全局变量的初始值 3.3.2 局部变量的初始值 3.4 与函数有关的优化 3.4.1 函数调用与程序优化 3.4.2 变量存储优化 3.5 编写多文件程序——变量的存储类别 3.5.1 存储类别 3.5.2 static变量的作用——改变变量的生命期 3.5.3 static变量的作用——实现封装和模块化设计 3.6 编写多文件的程序——链接的作用 3.6.1 链接多个文件 3.6.2 链接时符号解析规则 3.6.3 链接规则的应用 3.7 可变参数 3.7.1 可变参数的概念 3.7.2 实现一个简单的可变参数的函数 3.7.3 可变参数实例 3.7.4 关于printf函数的疑问——缺少整型参数 3.7.5 关于printf函数的疑问——缺少字符串地址参数 第4章 C语言中的指针与字符串 4.1 sizeof运算符 4.1.1 sizeof运算符的应用——得到内置类型的大小 4.1.2 sizeof运算符的应用——得到复合类型的大小 4.2 指针的应用 4.2.1 指针与别名陷阱 4.2.2 数组的指针 4.2.3 指针的指针 4.2.4 指针与参数传递 4.2.5 指针类型的意义 4.2.6 void*型指针 4.3 函数的指针 4.3.1 C语言中的函数指针 4.3.2 函数指针的应用——回调函数 4.3.3 函数指针数组 4.4 字符串 4.4.1 字符串与字符数组 4.4.2 字符串与指针 4.4.3 限定修饰符const 4.4.4 const关键字修饰指针——在指针定义之前 4.4.5 const关键字修饰指针——在指针定义之中 4.4.6 const关键字修饰指针——在指针定义之前和定义之中 4.4.7 使用const关键字的意义 第5章 C语言的高级技术 第2篇 C语言开发环境 第6章 vi与vim编辑器 第7章 gcc编译器 第8章 makefile 第9章 gdb 第3篇 Linux进程操作 第10章 进程环境 第11章 进程控制 第12章 时间和日历历程 第13章 信号及信号处理 第14章 进程间通信 第15章 线程 第4篇 Linux文件操作 第17章 文件I/O 第18章 文件管理 第19章 目录操作 第20章 特殊文件 第21章 基于流的I/O 第5篇 Linux网络编程 第22章 TCP和UDP协议 第23章 网络编程基础 第24章 网络编程进阶 第25章 网络编程实例——实现文件传输程序 第26章 网络编程实例——简单的Web服务器 第6篇 shell脚本知识 第27章 shell脚本基础 第28章 shell脚本中的控制结构 844
线程概念 什么是线程 LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下) 进程:独立地址空间,拥有PCB 线程:也有PCB,但没有独立的地址空间(共享) 区别:在于是否共享地址空间。 独居(进程);合租(线程)。 Linux下: 线程:最小的执行单位 进程:最小分配资源单位,可看成是只有一个线程的进程。 Linux内核线程实现原理 类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。 1. 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone 2. 从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的 3. 进程可以蜕变成线程 4. 线程可看做寄存器和栈的集合 5. 在linux下,线程最是小的执行单位;进程是最小的分配资源单位 察看LWP号:ps –Lf pid 查看指定线程的lwp号。 三级映射:进程PCB --> 页目录(可看成数组,首地址位于PCB中) --> 页表 --> 物理页面 --> 内存单元 参考:《Linux内核源代码情景分析》 ----毛德操 对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。 但!线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。 实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。 如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。 因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。 线程共享资源 1.文件描述符表 2.每种信号的处理方式 3.当前工作目录 4.用户ID和组ID 5.内存地址空间 (.text/.data/.bss/heap/共享库) 线程非共享资源 1.线程id 2.处理器现场和栈指针(内核栈) 3.独立的栈空间(用户空间栈) 4.errno变量 5.信号屏蔽字 6.调度优先级 线程优、缺点 优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便 缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好 优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。 线程控制原语 pthread_self函数 获取线程ID。其作用对应进程中 getpid() 函数。 pthread_t pthread_self(void); 返回值:成功:0; 失败:无! 线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同) 注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。 pthread_create函数 创建一个新线程。 其作用,对应进程中fork() 函数。 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。 参数: pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t; 参数1:传出参数,保存系统为我们分配好的线程ID 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。 参数4:线程主函数执行期间所使用的参数。 在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。star
Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。 目 录 第1部分C++ 多线程系统编程 第1章线程安全的对象生命期管理3 1.1当析构函数遇到多线程. . . . . . . . . . . . . . . . .. . . . . . . . . . . 3 1.1.1线程安全的定义. . . . . . . . . . . . . . . . .. . . . . . . . . . . 4 1.1.2MutexLock 与MutexLockGuard. . . . . . . . . . . . . . . . . . . . 4 1.1.3一个线程安全的Counter 示例.. . . . . . . . . . . . . . . . . . . 4 1.2对象的创建很简单. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 5 1.3销毁太难. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 7 1.3.1mutex 不是办法. . . . . . . . . . . . . . . . . . . .. . . . . . . . 7 1.3.2作为数据成员的mutex 不能保护析构.. . . . . . . . . . . . . . 8 1.4线程安全的Observer 有多难.. . . . . . . . . . . . . . . . . . . . . . . . 8 1.5原始指针有何不妥. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 11 1.6神器shared_ptr/weak_ptr . . . . . . . . . .. . . . . . . . . . . . . . . . 13 1.7插曲:系统地避免各种指针错误. . . . . . . . . . . . . . . . .. . . . . . 14 1.8应用到Observer 上.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.9再论shared_ptr 的线程安全.. . . . . . . . . . . . . . . . . . . . . . . . 17 1.10shared_ptr 技术与陷阱. . . .. . . . . . . . . . . . . . . . . . . . . . . . 19 1.11对象池. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 21 1.11.1enable_shared_from_this . . . . . . . . . . . . . . . . . . . . . . 23 1.11.2弱回调. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . 24 1.12替代方案. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 26 1.13心得与小结. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . 26 1.14Observer 之谬. . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 第2章线程同步精要 2.1互斥器(mutex). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.1.1只使用非递归的mutex . . . . . . . . . . . . . .. . . . . . . . . . 33 2.1.2死锁. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 35 2.2条件变量(condition variable). . . . . . . . . .

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值