多个线程同时访问共享数据时可能会冲突,比如两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成:

从内存读变量值到寄存器
寄存器的值加1
将寄存器的值写回内存

假设两个线程在多处理器平台上同时执行这三条指令,则可能导致下图所示的结果,最后变量只加了一次而非两次。

如下例子就演示了这一过程:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h> int counter; /* incremented by threads */ void *doit(void *vptr)
{
int i, val; for (i = 0; i < 100; i++) {
val = counter;
usleep(1000);
counter = val + 1;
} return NULL;
} int main(int argc, char **argv)
{
pthread_t tidA, tidB; pthread_create(&tidA, NULL, &doit, NULL);
pthread_create(&tidB, NULL, &doit, NULL); pthread_join(tidA, NULL);
pthread_join(tidB, NULL); printf("counter = %d \n", counter);
return 0;
}

在这个例子中,虽然每个线程都给counter加了100,但由于结果的互相覆盖,最终输出值不是200,而是100。

tianfang@linux-k36c:/mnt/share/test> run
counter = 100
tianfang@linux-k36c:/mnt/share/test>

互斥锁mutex

对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁(Mutex,Mutual Exclusive Lock),获得锁的线程可以完成”读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样”读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。

和mutext相关的函数有如下几个:

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

从名字中基本上就能看出来该如何使用,这里就只是以上面的例子为例,用mutex将其改造一下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h> int counter; /* incremented by threads */
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER; void *doit(void *vptr)
{
int i, val; for (i = 0; i < 100; i++) {
pthread_mutex_lock(&counter_mutex); val = counter;
usleep(1000);
counter = val + 1; pthread_mutex_unlock(&counter_mutex);
} return NULL;
} int main(int argc, char **argv)
{
pthread_mutex_init(&counter_mutex, NULL); pthread_t tidA, tidB; pthread_create(&tidA, NULL, &doit, NULL);
pthread_create(&tidB, NULL, &doit, NULL); pthread_join(tidA, NULL);
pthread_join(tidB, NULL); pthread_mutex_destroy(&counter_mutex); printf("counter = %d \n", counter);
return 0;
}

这次的运行结果就是正确的了:

tianfang@linux-k36c:/mnt/share/test> run
counter = 200
tianfang@linux-k36c:/mnt/share/test>

条件变量condtion

前面介绍的Mutext主要用于实现互斥,在多线程的场景中往往还有同步的需求。例如,在生产者和消费者的例子中,消费者需要等待生产者产生数据后才能消费。

单用mutex不能解决同步的问题:假如消费者先获取到锁,但此时并没有资源可用,如果把锁占着不放,生产者无法获取锁,此时就成了死锁;如果立即释放,并重新进入,则会成为轮询而消耗cpu资源。

在pthread库中通过可条件变量(Condition Variable)来阻塞等待一个条件,或者唤醒等待这个条件的线程。Condition Variable用pthread_cond_t类型的变量表示,其相关函数如下:

#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 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);

从它的wait函数可以看到,condtion是和mutext一起使用的,基本流程如下:

消费者获取资源锁,如果当前无可用资源则调用cond_wait函数释放锁,并等待condtion通知。
生产者产生资源后,获取资源锁,放置资源后嗲用cond_signal函数通知。并释放资源锁。
消费者的cond_wait函数等到condtion通知后,重新获取资源锁,消费资源后再次释放资源锁。

从中可以看到,mutex用于保护资源,wait函数用于等待信号,signal和broadcast函数用于通知信号。其中wait函数中有一次对mutex的释放和重新获取操作,因此生产者和消费者并不会出现死锁。

#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <queue>
#include <iostream>
using namespace std; queue<int> buffer;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void* consumer(void *p)
{
while(true)
{
sleep(rand() % 5);
pthread_mutex_lock(&lock);
if (buffer.empty())
pthread_cond_wait(&has_product, &lock); cout << "### Consume " << buffer.front() << endl;
buffer.pop(); pthread_mutex_unlock(&lock); }
} void* producer(void *p)
{
while(true)
{
sleep(rand() % 5);
pthread_mutex_lock(&lock); buffer.push(rand() % 1000);
cout << ">>> Produce " << buffer.back() << endl; pthread_cond_signal(&has_product);
pthread_mutex_unlock(&lock);
}
} 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;
}

信号量Semaphore

Mutex变量是非0即1的,可看作一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源,加锁时获得该资源,将Mutex减到0,表示不再有可用资源,解锁时释放该资源,将Mutex重新加到1,表示又有了一个可用资源。

信号量(Semaphore)和Mutex类似,表示可用资源的数量,和Mutex不同的是这个数量可以大于1。其相关操作函数是:

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t * sem);
int sem_destroy(sem_t * sem);

这里以一个有限资源池的生产者和消费者的例子演示一下信号量的用法:

#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h> #define NUM 5
int queue[NUM];
sem_t blank_number, product_number; void *producer(void *arg)
{
int p = 0;
while (true)
{
sem_wait(&blank_number);
queue[p] = rand() % 1000 + 1;
printf(">>> Produce %d\n", queue[p]);
sem_post(&product_number); p = (p+1) % NUM;
sleep(rand()%5);
}
} void *consumer(void *arg)
{
int c = 0;
while (true)
{
sem_wait(&product_number);
printf("### Consume %d\n", queue[c]);
queue[c] = 0;
sem_post(&blank_number); c = (c+1) % NUM;
sleep(rand() % 5);
}
} int main(int argc, char *argv[])
{
pthread_t pid, cid; sem_init(&blank_number, 0, NUM);
sem_init(&product_number, 0, 0); pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL); sem_destroy(&blank_number);
sem_destroy(&product_number); return 0;
}

读写锁(Reader-Writer Lock)

读写锁也是一种线程间的互斥操作,但和互斥锁不同的是,它分为读和写两种模式,其中写模式是独占资源的(和互斥锁一样),而读模式则是共享资源的,此时允许多个读者,从而提高并发性。

由于一时找不到合适的小例子来介绍它,并且读写锁的模型也是比较容易理解的,这里就不举例了。

Linux高级编程--09.线程互斥与同步的更多相关文章

  1. linux c编程:线程互斥一

    当多个线程共享相同的内存的时候,需要确保每个线程都看到一致的数据视图.如果每个线程使用的变量都是其他线程不会读取和修改的.那么就不存在一致性问题.同样,如果变量是只读的,多个线程也不会有一致性的问题. ...

  2. Linux高级编程--08.线程概述

    线程 有的时候,我们需要在一个基础中同时运行多个控制流程.例如:一个图形界面的下载软件,在处理下载任务的同时,还必须响应界面的对任务的停止,删除等控制操作.这个时候就需要用到线程来实现并发操作. 和信 ...

  3. linux c编程:线程互斥二 线程死锁

    死锁就是不同的程序在运行时因为某种原因发生了阻塞,进而导致程序不能正常运行.阻塞程序的原因通常都是由于程序没有正确使用临界资源. 我们举个日常生活中的例子来比喻死锁.我们把马路上行驶的汽车比作运行着的 ...

  4. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  5. 【UNIX环境高级编程】线程同步

    当多个线程共享相同的内存时,需要确保每个线程看到一致的数据视图.如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题.同样,如果变量是只读的也不会有一致性问题.但是,当一个线程可 ...

  6. python多线程编程(3): 使用互斥锁同步线程

    问题的提出 上一节的例子中,每个线程互相独立,相互之间没有任何关系.现在假设这样一个例子:有一个全局的计数num,每个线程获取这个全局的计数,根据num进行一些处理,然后将num加1.很容易写出这样的 ...

  7. C++线程互斥、同步

     一.线程互斥 如果多个线程需要访问且可能修改同一个变量,那么需要加锁,保证同一时刻只有一个线程可以访问,这个动作即最小“原子操作” 方式1: 使用c++提供的类mutex,lock,unlock即可 ...

  8. linux系统编程:线程同步-相互排斥量(mutex)

    线程同步-相互排斥量(mutex) 线程同步 多个线程同一时候訪问共享数据时可能会冲突,于是须要实现线程同步. 一个线程冲突的演示样例 #include <stdio.h> #includ ...

  9. linux系统编程:线程同步-信号量(semaphore)

    线程同步-信号量(semaphore) 生产者与消费者问题再思考 在实际生活中,仅仅要有商品.消费者就能够消费,这没问题. 但生产者的生产并非无限的.比如,仓库是有限的,原材料是有限的,生产指标受消费 ...

随机推荐

  1. paip.uapi 获取网络url内容html 的方法java php ahk c++ python总结.

    paip.uapi 获取网络url内容html 的方法java php ahk c++ python总结. 各种语言总结比较,脚本php.python果然是方便.简短,实用. uapi : get_w ...

  2. GUID全局唯一标识符相关知识了解

     全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符.GUID主要用于在拥有多个节点.多台计算机的网络或系统中.在理想情 ...

  3. Hibernate入门4.核心技能

    Hibernate入门4.核心技能 20131128 代码下载 链接: http://pan.baidu.com/s/1Ccuup 密码: vqlv 前言: 前面学习了Hibernate3的基本知识, ...

  4. android: SQLite升级数据库

    如果你足够细心,一定会发现 MyDatabaseHelper 中还有一个空方法呢!没错,onUpgrade() 方法是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用,可 千万不 ...

  5. ios相关手册、图表等综合

    Objective-C初学者速查表(来源:http://www.cocoachina.com/applenews/devnews/2013/1115/7362.html) iOS UIKit类图 (来 ...

  6. 2016年 最火的 15 款 HTML5 游戏引擎

    HTML5游戏从2014年Egret引擎开发的神经猫引爆朋友圈之后,就开始一发不可收拾,今年<传奇世界>更是突破流水2000万!从两年多的发展来看,游戏开发变得越来越复杂,需要制作各种炫丽 ...

  7. 用C#表达式树优雅的计算24点

    思路:一共4个数字,共需要3个运算符,可以构造一个二叉树,没有子节点的节点的为值,有叶子节点的为运算符 例如数字{1, 2, 3, 4},其中一种解的二叉树形式如下所示: 因此可以遍历所有二叉树可能的 ...

  8. 使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程(三)

    这几篇都是我原来首发在 segmentfault 上的地址:https://segmentfault.com/a/1190000005040834 突然想起来我这个博客冷落了好多年了,也该更新一下,呵 ...

  9. S7-200系列PLC与WINCC以太网通信CP243i的实例

    S7-200系列PLC与WINCC以太网通信CP243i的实例 ----选用大连德嘉国际电子www.dl-winbest.cn的CP243i作为连接S7-200的PPI口转以太网RJ45的接口转换器. ...

  10. Codeforces Round #385 (Div. 2) B - Hongcow Solves A Puzzle 暴力

    B - Hongcow Solves A Puzzle 题目连接: http://codeforces.com/contest/745/problem/B Description Hongcow li ...