7.10-第十课:线程同步

================

第十课  线程同步

================

一、竞争与同步

--------------

当多个线程同时访问其所共享的进程资源时,

需要相互协调,以防止出现数据不一致、

不完整的问题。这就叫线程同步。

范例:vie.c

理想中的原子++:

-----------------+-----------------+------

线程1     |       线程2     | 内存

--------+--------+--------+--------+------

指  令 | 寄存器 | 指  令 | 寄存器 | g_cn

--------+--------+--------+--------+------

读内存 |    0   |        |        |   0

算加法 |    1   |        |        |   0

写内存 |    1   |        |        |   1

|        | 读内存 |    1   |   1

|        | 算加法 |    2   |   1

|        | 写内存 |    2   |   2

--------+--------+--------+--------+------

现实中的非原子++:

-----------------+-----------------+------

线程1     |       线程2     | 内存

--------+--------+--------+--------+------

指  令 | 寄存器 | 指  令 | 寄存器 | g_cn

--------+--------+--------+--------+------

读内存 |    0   |        |        |   0

|        | 读内存 |    0   |   0

算加法 |    1   |        |        |   0

|        | 算加法 |    1   |   0

写内存 |    1   |        |        |   1

|        | 写内存 |    1   |   1

--------+--------+--------+--------+------

二、互斥量

----------

int pthread_mutex_init (pthread_mutex_t* mutex,

const pthread_mutexattr_t* mutexattr);

亦可

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int pthread_mutex_lock (pthread_mutex_t* mutex);

int pthread_mutex_unlock (pthread_mutex_t* mutex);

int pthread_mutex_destroy (pthread_mutex_t* mutex);

1) 互斥量被初始化为非锁定状态;

2) 线程1调用pthread_mutex_lock函数,立即返回,

互斥量呈锁定状态;

3) 线程2调用pthread_mutex_lock函数,阻塞等待;

4) 线程1调用pthread_mutex_unlock函数,

互斥量呈非锁定状态;

5) 线程2被唤醒,从pthread_mutex_lock函数中返回,

互斥量呈锁定状态;

...

范例:mutex.c

三、信号量

----------

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

#include <semaphore.h>

// 创建信号量

int sem_init (sem_t* sem, int pshared,

unsigned int value);

sem     - 信号量ID,输出。

pshared - 一般取0,表示调用进程的信号量。

非0表示该信号量可以共享内存的方式,

为多个进程所共享(Linux暂不支持)。

value   - 信号量初值。

// 信号量减1,不够减即阻塞

int sem_wait (sem_t* sem);

// 信号量减1,不够减即返回-1,errno为EAGAIN

int sem_trywait (sem_t* sem);

// 信号量减1,不够减即阻塞,

// 直到abs_timeout超时返回-1,errno为ETIMEDOUT

int sem_timedwait (sem_t* sem,

const struct timespec* abs_timeout);

struct timespec {

time_t tv_sec;  // Seconds

long   tv_nsec; // Nanoseconds [0 - 999999999]

};

// 信号量加1

int sem_post (sem_t* sem);

// 销毁信号量

int sem_destroy (sem_t* sem);

范例:sem.c

注意:

1) 信号量APIs没有声明在pthread.h中,

而是声明在semaphore.h中,失败也不返回错误码,

而是返回-1,同时设置errno。

2) 互斥量任何时候都只允许一个线程访问共享资源,

而信号量则允许最多value个线程同时访问共享资源,

当value为1时,与互斥量等价。

范例:pool.c

四、死锁问题

------------

线程1   线程2

|       |

获取A   获取B

|       |

获取B   获取A <- 死锁

\ /

释放B X 释放A

/ \

释放A   释放B

范例:dead.c

五、条件变量

------------

生产者消费者模型

生产者:产生数据的线程。

消费者:使用数据的线程。

通过缓冲区隔离生产者和消费者,与二者直连相比,

避免相互等待,提高运行效率。

生产快于消费,缓冲区满,撑死。

消费快于生产,缓冲区空,饿死。

条件变量可以让调用线程在满足特定条件的情况下暂停。

int pthread_cond_init (pthread_cond_t* cond,

const pthread_condattr_t* attr);

亦可

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 使调用线程睡入条件变量cond,同时释放互斥锁mutex

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);

struct timespec {

time_t tv_sec;  // Seconds

long   tv_nsec; // Nanoseconds [0 - 999999999]

};

// 从条件变量cond中唤出一个线程,

// 令其重新获得原先的互斥锁

int pthread_cond_signal (pthread_cond_t* cond);

注意:被唤出的线程此刻将从pthread_cond_wait函数中返回,

但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。

// 从条件变量cond中唤出所有线程

int pthread_cond_broadcast (pthread_cond_t* cond);

int pthread_cond_destroy (pthread_cond_t* cond);

范例:cond.c

注意:当一个线程被从条件变量中唤出以后,

导致其睡入条件变量的条件可能还需要再判断一次,

因其随时有可能别其它线程修改。

范例:bc.c (if->while)

六、哲学家就餐问题

------------------

1965年,著名计算机科学家艾兹格·迪科斯彻,提出并解决

了一个他称之为哲学家就餐的同步问题。从那时起,每个发

明同步原语的人,都希望通过解决哲学家就餐问题来展示其

同步原语的精妙之处。

这个问题可以简单地描述如下:

五个哲学家围坐在一张圆桌周围,每个哲学家面前都有一盘

通心粉。由于通心粉很滑,所以需要两把叉子才能夹住。相

邻两个盘子之间放有一把叉子。哲学家的生活中有两种交替

活动时段:即吃饭和思考。当一个哲学家觉得饿了时,他就

试图分两次去取其左边和右边的叉子,每次拿一把,但不分

次序。如果成功地得到了两把叉子,就开始吃饭,吃完后放

下叉子继续思考。

图示:dining.png

关键问题是:能为每一个哲学家写一段描述其行为的程序,

且决不会死锁吗?

提示:

如果五位哲学家同时拿起左面的叉子,就没有人能够拿到他

们各自右面的叉子,于是发生了死锁。

如果每位哲学家在拿到左面的叉子后,发现其右面的叉子不

可用,那么就先放下左面的叉的,等待一段时间,再重复此

过程。可能在某一个瞬间,所有的哲学家都同时拿起左叉,

看到右叉不可用,又都放下左叉,等一会儿,又都同时拿起

左叉,如此重复下去。虽然程序在不停运行,但都无法取得

进展,于是发生了活锁。

思路:

解决问题的关键在于,必须保证任意一位哲学家只有在其左

右两个邻居都没有在进餐时,才允许其进入进餐状态。这样

做不仅不会发生死锁,而且对于任意数量的哲学家都能获得

最大限度的并行性。

范例:dining.c

来自为知笔记(Wiz)

原文地址:https://www.cnblogs.com/xuxaut-558/p/10041730.html

时间: 2024-10-17 22:37:50

7.10-第十课:线程同步的相关文章

Java并发学习之十九——线程同步工具之Phaser

本文是学习网络上的文章时的总结.感谢大家无私的分享. JDK 1.7 加入了一个新的工具Phaser.Phaser的在功能上与CountDownLatch有部分重合. 以下使用Phaser类来同步3个并发任务. 这3个任务会在3个不同的目录和它们的子目录中搜索扩展名是.log的文件. 这个任务被分成3个步骤: 1. 在指定的目录和子目录中获得文件扩展名为.log的文件列表. 2. 在操控台打印结果. 在步骤1和步骤2的结尾我们要检查列表是否为空. 假设为空.那么线程直接结束执行并从phaser类

Java并发学习之十八——线程同步工具之CyclicBarrier

本文是学习网络上的文章时的总结,感谢大家无私的分享. CyclicBarrier 类有一个整数初始值,此值表示将在同一点同步的线程数量.当其中一个线程到达确定点,它会调用await() 方法来等待其他线程.当线程调用这个方法,CyclicBarrier阻塞线程进入休眠直到其他线程到达.当最后一个线程调用CyclicBarrier 类的await() 方法,它唤醒所有等待的线程并继续执行它们的任务. 注意比较CountDownLatch和CyclicBarrier: 1.CountDownLatc

Java并发学习之十六——线程同步工具之信号量(Semaphores)

本文是学习网络上的文章时的总结,感谢大家无私的分享. 当一个线程想要访问某个共享资源,首先,它必须获得semaphore.如果semaphore的内部计数器的值大于0,那么semaphore减少计数器的值并允许访问共享的资源.计数器的值大于0表示,有可以自由使用的资源,所以线程可以访问并使用它们. package chapter3; import java.util.concurrent.Semaphore; public class PrintQueue2 { private final Se

线程同步(windows平台):互斥对象

一:介绍 互斥对象是系统内核维护的一种数据结构,保证了对象对单个线程的访问权. 二:函数说明 创建互斥对象:    HANDLE CreateMutex(            LPSECURITY_ATTRIBUTES lpMutexAttributes, 安全属性结构指针,可为NULL,表示默认安全性            BOOL bInitialOwner,     //是否占有该互斥量,TRUE:占有,FALSE:不占有            LPCTSTR lpName       

线程同步(windows平台):事件

一:介绍 事件Event实际上是个内核对象,事件分两种状态:激发状态和未激发状态.分两种类型:手动处置事件和自动处置事件.手动处置事件被设置为激发状态后,会唤醒所有等待的线程,一直保持为激发状态,直到把它设置为未激发状态.自动处置事件被设置为激发状态后,会唤醒一个等待中的线程,然后会自动设置成未激发状态. 二:函数说明 创建事件对象:    HANDLE CreateEvent    (     LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性  

线程同步(windows平台):信号量

一:介绍 信号量也是系统核心对象,它允许多个线程同一时刻访问同一资源,但需限制同一时刻访问资源的最大线程数目. 信号量遵循规则:1.当前资源计数大于0,信号量有效.2.当前资源计数等于0,信号量无效.3.系统不允许当前资源数量为负.4.当前资源数量不能大于最大资源数量. 二:函数说明 创建信号量:    HANDLE   CreateSemaphore(         LPSECURITY ATTRIBUTES   lpSemaphoreAttributes,     //安全属性      

python学习第十课 多路复用、ThreadingTCPServer、线程与进程

python 第十课 多路复用 I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作 select    poll          epoll 网络操作.文件操作.终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测.如普通文件操作自动上次读取是否已经变化.所以主要用来网络操作 windows 和 mac的python 只提供select,linux上的python

NEU操作系统实验课4——线程同步

实验要求: 创建两个线程按数字顺序打印10以下自然数,其中一个线程打印1-3及8-10:另一个线程打印4-6.要求使用线程同步机制实现上述打印顺序. 看网上的资料学习了两种写法 C++11的,使用了std::thread 1 #include <unistd.h> 2 #include <thread> 3 #include <iostream> 4 #include <mutex> 5 #include<bits/stdc++.h> 6 voi

JAVA 并发编程-线程同步通信技术(Lock和Condition)(十)

在之前的博客中已经介绍过线程同步通信技术<JAVA 并发编程-传统线程同步通信技术(四)>,上篇是使用的synchronized,wait,notify来实现,今天我们使用的是Lock和Condition,下面我们结合两者对比来学习. 简单的Lock锁应用: /** * 简单Lock的应用 * @author hejingyuan * */ public class LockTest { public static void main(String[] args) { new LockTest