- 浏览: 76967 次
- 性别:
- 来自: 长沙
最新评论
-
3258964:
若不理解,可参考http://www.srccode.cn/a ...
IOC设计模式的理解 -
luhouxiang:
最后的示例代码有点怪,BigBm 这个根本没用到classA ...
IOC设计模式的理解 -
wyh471738293:
你好,请问int y1192 = 1192 * y;int r ...
《android中对camera数据的简单编码处理》 -
ihopethatwell:
期待更详细
C:Android camera S:PC opencV阶段总结 -
sunleije:
重点没有啊,想看识别怎么和opencv交互的
C:Android camera S:PC opencV阶段总结
前面专门找了一些资料去了解函数的可重入性以及线程安全同步,我们知道在多线程并发访问共享数据时候往往会出现数据冲突,这跟之前的函数可重入性是一样的.比如两个线程要对一个全局变量加1.但这个过程在x86平台上并非原子操作,它可能会经过以下三步才能完成:
1:从内存中读取变量到寄存器中
2:寄存器加1
3:将寄存器的值写回内存
这一个非原子操作可想而知,如果有多线程并发执行,那么这三步有可能就在线程间交叉执行.如:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define NLOOP 5000 int counter; /* incremented by threads */ void *doit(void *); int main(int argc, char **argv) { pthread_t tidA, tidB; pthread_create(&tidA, NULL, &doit, NULL); pthread_create(&tidB, NULL, &doit, NULL); /* wait for both threads to terminate */ pthread_join(tidA, NULL); pthread_join(tidB, NULL); return 0; } void *doit(void *vptr) { int i, val; /* * Each thread fetches, prints, and increments the counter NLOOP times. * The value of the counter should increase monotonically. */ for (i = 0; i < NLOOP; i++) { val = counter; printf("%x: %d\n", (unsigned int)pthread_self(), val + 1); counter = val + 1; } return NULL; }由#define NLOOP 5000
可知,我们创建的两个线程分别对counter进行了5000次加1操作,最终结果应该是10000才对,但我们看输出却是:
6cb6c700: 1
6cb6c700: 2
6cb6c700: 3
6cb6c700: 4
6cb6c700: 5
6cb6c700: 6
6cb6c700: 7
6cb6c700: 8
6cb6c700: 9
6cb6c700: 10
6cb6c700: 11
6cb6c700: 12
6cb6c700: 13
6cb6c700: 14
6cb6c700: 15
6cb6c700: 16
6cb6c700: 17
6cb6c700: 18
6cb6c700: 19
6cb6c700: 20
6cb6c700: 21
6d36d700: 1
6d36d700: 2
6d36d700: 3
6d36d700: 4
6d36d700: 5
6d36d700: 6
6d36d700: 7
6d36d700: 8
6d36d700: 9
6d36d700: 10
6d36d700: 11
6d36d700: 12
6d36d700: 13
6d36d700: 14
6cb6c700: 22
6cb6c700: 23
6cb6c700: 24
6cb6c700: 25
6cb6c700: 26
6cb6c700: 27
6cb6c700: 28
6cb6c700: 29
6cb6c700: 30
6cb6c700: 31
6cb6c700: 32
6cb6c700: 33
.......................
这个输出的不同会因不同的CPU调度,总线周期而定.
普遍情况下,我们为保证共享数据在多线程访问下的正确性,会引入互斥锁(Mutex Exclusive Lock,它与自旋锁的区别请百度).引入锁在目的在于使线程对数据的"读-修改-写"形成一组原子操作,要么执行完,要么不执行,不能被打断
使用锁的基本过程一般包括,1:锁初始化 2:取得锁 3:释放锁,对于互斥锁有如下函数声名
Mutex用pthread_mutex_t 类型的变量表示,可以这样初始化和销毁: #include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 返回值:成功返回0,失败返回错误号。 pthread_mutex_init 函数对Mutex做初始化,参数attr 设定Mutex的属性,如果attr 为NULL 则表 示缺省属性,本章不详细介绍Mutex属性,感兴趣的读者可以参考[APUE2e]。 用pthread_mutex_init 函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量 是静态分配的(全局变量或static变量),也可以用宏定义PTHREAD_MUTEX_INITIALIZER 来初始 化,相当于用pthread_mutex_init 初始化并且attr 参数为NULL 。Mutex的加锁和解锁操作可以用 下列函数: #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 返回值:成功返回0,失败返回错误号。 一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调 用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调 用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。 如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经 被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
现在我们就用Mutex来改写一下刚才的程序:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define NLOOP 5000 int counter; /* incremented by threads */ pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER; void *doit(void *); int main(int argc, char **argv) { pthread_t tidA, tidB; pthread_create(&tidA, NULL, doit, NULL); pthread_create(&tidB, NULL, doit, NULL); /* wait for both threads to terminate */ pthread_join(tidA, NULL); pthread_join(tidB, NULL); return 0; } void *doit(void *vptr) { int i, val; /* * Each thread fetches, prints, and increments the counter NLOOP times. * The value of the counter should increase monotonically. */ for (i = 0; i < NLOOP; i++) { pthread_mutex_lock(&counter_mutex);//如果了解了函数的可重入性与线程安全我们就知道,我们的锁应该是加在全局变量上
val = counter;//counter全局变量是导致非线程安全的所在 printf("%x: %d\n", (unsigned int)pthread_self(), val + 1); counter = val + 1; pthread_mutex_unlock(&counter_mutex);//释放锁 } return NULL; }
这样运行一下就可得到10000的正确结果.
在这里简单初充一下Mutex的简单实现原理,
当Mutex为1时表示锁空闲(未被取得),为0时表示锁已被取得.当线程进行lock() or trylock()操作时,如果Mutex为1,则被取得.
如果为0时,那么调用lock的线程则会被挂起,如果不想被挂起则调用trylock().
lock: if(mutex > 0){ mutex = 0; return 0; } else 挂起等待; goto lock; unlock: mutex = 1; 唤醒等待Mute
而对于unlock()操作可以有多种实现,可以只唤醒一个等待线程,也可以让等待线程队列去竞争这个锁资料(可能按优先级,FIFO等调度算法去选择).
由以上可知,我们引进Mutex是为了实现一组原子操作,这个互斥过程在大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存
器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问
内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待
总线周期。现在我们把lock和unlock的伪代码改一下(以x86的xchg指令为例):
lock: movb $0, %al xchgb %al, mutex if(al寄存器的内容 > 0){ return 0; } else 挂起等待; goto lock; unlock: movb $1, mutex 唤醒等待Mutex的线程; return 0;
unlock中的释放锁操作同样只用一条指令实现,以保证它的原子性。
Condition Variable
线程间的同步还有这样一种情况:线程A需要等某个条件成立才能继续往下执行,现在这个条件
不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执
行。在pthread库中通过条件变量(Condition Variable)来阻塞等待一个条件,或者唤醒等待这
个条件的线程。Condition Variable用pthread_cond_t 类型的变量表示,可以这样初始化和销
毁:
#include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond); int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
返回值:成功返回0,失败返回错误号。
和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr 参
数为NULL 则表示缺省属性,pthread_cond_destroy 函数销毁一个Condition Variable。如
果Condition Variable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER 初始化,相当于
用pthread_cond_init函数初始化并且attr 参数为NULL 。Condition Variable的操作可以用下列函
数:
#include <pthread.h> int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);
返回值:成功返回0,失败返回错误号。
可见,一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调
用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,重新获得Mutex并返回
pthread_cond_timedwait 函数还有一个额外的参数可以设定等待超时,如果到达了abstime 所指
定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT 。一个线程可以调
用pthread_cond_signal 唤醒在某个Condition Variable上等待的另一个线程,也可以调
用pthread_cond_broadcast唤醒在这个Condition Variable上等待的所有线程。
下面的程序演示了一个生产者-消费者的例子,生产者生产一个结构体串在链表的表头上,消费
者从表头取走结构体。
#include <stdlib.h> #include <pthread.h> #include <stdio.h> struct msg { struct msg *next; int num; }; struct msg *head; pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void *consumer(void *p) { struct msg *mp; for (;;) { pthread_mutex_lock(&lock); while (head == NULL) pthread_cond_wait(&has_product, &lock); mp = head; head = mp->next; pthread_mutex_unlock(&lock); printf("Consume %d\n", mp->num); free(mp); sleep(rand() % 5); } } void *producer(void *p) { struct msg *mp; for (;;) { mp = malloc(sizeof(struct msg)); mp->num = rand() % 1000 + 1; printf("Produce %d\n", mp->num); pthread_mutex_lock(&lock); mp->next = head; head = mp; pthread_mutex_unlock(&lock); pthread_cond_signal(&has_product); sleep(rand() % 5); } } int main(int argc, char *argv[]) { pthread_t pid, cid; srand(time(NULL)); pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); return 0; } 执行结果如下: $ ./a.out Produce 744 Consume 744 Produce 567 Produce 881 Consume 881 Produce 911 Consume 911 Consume 567 Produce 698 Consume 698
发表评论
-
linux c实现的简单web响应程序(会完善成一个简单web服务器)
2013-01-13 14:45 3773#include <stdio.h> #i ... -
Condition Variable与Mutex搭配的前因后果
2012-12-21 21:01 3488本来打算用一个形象的生活情况来比喻条件互斥的,但想了再想实在没 ... -
可重入与线程安全
2012-12-10 15:37 1002可重入 & 信号安 ... -
自旋锁
2012-12-21 21:09 810自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大 ... -
Linux信号(signal) 机制分析
2012-11-17 17:27 9153分享 应用程序 公共主页 人人生 ... -
linux进程间通信(IPC)与控制---管道
2012-11-16 22:41 1256进程有独立的用户内存地址, 进程的全局变量对其它进程透明, ... -
Linux自定义系统调用
2012-11-16 12:05 906Linux自定义系统调用 ... -
linux系统调用
2012-11-15 18:20 1235目录: 1. Linux系统调用原理 2. 系统调用 ... -
strace实现原理(ptrace)
2012-11-15 17:37 2012strace实现原理 ... -
运用exec与dup2写的小测试
2012-11-15 16:44 1138上代码: /* upper.c */ #includ ... -
fork函数
2012-11-15 15:50 1073fork,分叉之意,这是因为 ... -
exec系列函数介绍
2012-11-15 15:51 952用fork 创建子进 ... -
VFS中的file_operations与inode的关系
2012-11-13 11:59 3352每个进程在PCB(Process Contro ... -
ext2文件系统结构体组成
2012-11-13 11:19 2646The Second Extended File System ... -
Linux Shell编程之U盘加载与卸载
2012-11-12 19:22 4105额,也不记得是什么时候写的了···偶然翻到···拿出来供学习而 ... -
解决fedora16亮度调度,双显卡用户切换问题
2012-11-12 19:16 659ati显卡调度亮度,因fedora16是在grub2引导的,所 ... -
ext2文件系统解构探析
2012-11-12 19:15 988内容要点: 超级 ...
相关推荐
1.使用三种VC的多线程同步方法编写一个多线程的程序(要求在屏幕上先显示Hello,再显示World)。 1)基于全局变量的多线程同步程序; 2)基于事件的多线程同步程序; 3)基于临界区的多线程同步程序。
在C++编程中,多线程同步是一种关键的技术,它允许多个执行线程协同工作,以避免数据竞争和死锁等并发问题。信号量(Semaphore)是实现多线程同步的一种有效工具,常用于控制对共享资源的访问。在这个名为"Mthread11...
"Java多线程同步.pdf" Java多线程同步是指在Java语言中,如何使用synchronized关键字和其他同步机制来确保多线程程序的正确执行。在Java语言中,synchronized关键字用于对方法或者代码块进行同步,但是仅仅使用...
本示例“简单实现多线程同步示例(模拟购票系统)”旨在通过一个具体的实例,帮助开发者理解如何在Java中创建并管理多线程以及如何实现线程同步,确保数据的一致性和正确性。 首先,我们要明确多线程的基本概念。在...
操作系统中的多线程同步是一个关键概念,特别是在并发编程中,它涉及到如何协调多个线程以避免数据不一致性和竞态条件。在这个实验中,我们关注的是C++编程语言中的实现,以及操作系统如何处理线程的优先级。 首先...
【MFC多线程同步类的使用】 在MFC(Microsoft Foundation Classes)中,多线程编程是一项重要的技术,尤其在开发复杂的、并发执行的任务时。多线程允许程序同时执行多个任务,提升效率和响应速度。然而,线程间的...
本文将深入探讨如何在C#中实现多线程同步并发操作,这不仅对于提高软件性能至关重要,也是高级程序员必须掌握的核心技能之一。 ### C#中的多线程同步并发操作 多线程编程可以极大地提高CPU的利用率,特别是在处理I...
多线程同步是保证多个线程安全交互的关键技术,避免了数据竞争和死锁等问题。本实例主要探讨了四种主要的同步机制:信号量、互斥锁、事件以及临界资源。 首先,信号量(Semaphore)是一种计数型同步对象,用于控制...
本文将深入探讨如何使用JNI实现多线程同步,并通过源码来解析这一过程。 1. **JNI基础知识** JNI为Java程序员提供了一种方式,可以在Java代码中调用本地方法,反之亦然。它定义了一系列的函数,例如`FindClass`...
### Windows多线程间同步事件的控制方法 #### 引言 随着计算机处理能力的提升以及软件复杂度的增加,多线程编程已成为现代软件开发不可或缺的一部分。在Windows平台上,多线程应用允许开发者充分利用多核处理器的...
在Windows操作系统中,多线程同步是一个至关重要的概念,它涉及到如何有效地管理和协调多个线程在共享资源时的执行顺序,以避免数据竞争和其他并发问题。这篇实验报告和相关的C++代码着重探讨了这一主题。 多线程是...
本篇文章将深入探讨三种在C++中实现多线程同步的方法:事件对象、关键代码段和互斥对象。 首先,我们来看**事件对象**。事件对象是一种信号机制,用于线程间通信和同步。在Windows API中,CreateEvent函数创建一个...
以下是对这两个函数及其在多线程同步演示中的应用的详细解释。 首先,`CreateThread()`函数用于创建一个新的线程来执行指定的函数,即线程的入口点。该函数接收一系列参数,包括线程函数指针、初始线程堆栈大小、...
本文将深入探讨在.NET框架中用于多线程同步的三个主要工具:Monitor、Mutex和EventWaitHandle,并结合提供的"五个多线程同步应用小程序",解释它们的应用场景和使用方法。 首先,我们来看Monitor类。Monitor是.NET...
本Demo主要关注四种多线程同步机制:临界区(CriticalSection)、互斥量(Mutex)、事件(Event)以及信号量(Semaphore)。下面将详细阐述这些概念及其在实际应用中的作用。 1. 临界区(CriticalSection) 临界区...
本示例着重讲解了VC++中的多线程同步,这是多线程编程中确保数据安全和正确性的重要概念。我们将深入探讨临界区、互斥量、事件和信号量这四种多线程同步机制。 1. **临界区(Critical Section)**:临界区是多线程...
本文将深入探讨Delphi中的多线程和线程同步,并以"SortThreads"和"delphi-thread-gui"这两个示例项目为例,讲解如何在实践中应用这些概念。 1. **多线程**:多线程允许应用程序同时执行多个独立的任务,提高程序的...
在计算机编程中,多线程同步是一个至关重要的概念,特别是在处理并发执行的任务时。多线程操作使得程序能够同时执行多个不同的任务,从而提高效率和响应速度。在本例中,"多线程同步"的标题暗示我们关注的是如何在多...
"临界区"和"多线程同步"是解决这一问题的关键概念。临界区是一种同步机制,它允许一次只有一个线程访问特定的代码区域或资源,以避免并发访问时可能产生的数据竞争和不一致性。多线程同步则是为了协调多个线程的执行...
在编程领域,尤其是在Java这样的多线程环境中,理解和掌握多线程同步与通讯至关重要。本文将深入探讨这些概念,以及如何使用synchronized关键字、wait-notify机制和Lock接口来实现线程间的同步与通讯。 首先,多...