UNIX再学习 -- 线程同步
一、为什么要线程同步
一、互斥量
1、什么是互斥量?
原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。
唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。
非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。
2、互斥量使用步骤
(1)定义互斥量
pthread_mutex_t mutex
(2)初始化互斥锁
pthread_mutex_t mtx = PTHERAD_MUTEX_INITIALIZER
动态初始化
int pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
返回值:成功返回 0;失败返回错误编号
要用默认的属性初始化互斥量,只需把 attr 设为 NULL。
如果互斥锁已初始化,则它会处于未锁定状态。互斥锁可以位于进程之间共享的内存中或者某个进程的专用内存中。
(3)使用互斥量进行加锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
返回值:若成功返回 0,;失败返回错误编号
对互斥量进行加锁,需要调用 pthread_mutex_lock。如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。
(4)使用互斥量进行解锁
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:若成功返回 0,;失败返回错误编号
(5)如果不再使用,则销毁互斥量
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:成功返回 0;失败返回错误码
3、示例说明
//使用互斥量解决多线程抢占资源的问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>char* buf[5]; //字符指针数组 全局变量
int pos; //用于指定上面数组的下标//1.定义互斥量
pthread_mutex_t mutex;void* task(void* p)
{//3.使用互斥量进行加锁pthread_mutex_lock(&mutex);buf[pos] = p;sleep(1);pos++;//4.使用互斥量进行解锁pthread_mutex_unlock(&mutex);
}int main(void)
{//2.初始化互斥量pthread_mutex_init(&mutex,0);//1.启动一个线程 向数组中存储内容pthread_t tid,tid2;pthread_create(&tid,NULL,task,"zhangfei");pthread_create(&tid2,NULL,task,"guanyu");//2.主线程进程等待,并且打印最终的结果pthread_join(tid,NULL);pthread_join(tid2,NULL);//5.销毁互斥量pthread_mutex_destroy(&mutex);int i = 0;printf("字符指针数组中的内容是:");for(i = 0; i < pos; i++){printf("%s ",buf[i]);}printf("\n");return 0;
}编译:# gcc test.c -lpthread
输出结果:
字符指针数组中的内容是:guanyu zhangfei
4、示例解析
多线程抢占资源,zhangfei、guanyu 线程阻塞不能同时给数组赋值的,所以用到互斥锁。
三、避免死锁
1、什么是死锁呢?
//使用互斥量解决多线程抢占资源的问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>char* buf[5]; //字符指针数组 全局变量
int pos; //用于指定上面数组的下标//1.定义互斥量
pthread_mutex_t mutex;void* task(void* p)
{//3.使用互斥量进行加锁pthread_mutex_lock(&mutex);pthread_mutex_lock(&mutex);buf[pos] = p;sleep(1);pos++;//4.使用互斥量进行解锁pthread_mutex_unlock(&mutex);pthread_mutex_unlock(&mutex);
}int main(void)
{//2.初始化互斥量pthread_mutex_init(&mutex,0);//1.启动一个线程 向数组中存储内容pthread_t tid,tid2;pthread_create(&tid,NULL,task,"zhangfei");pthread_create(&tid2,NULL,task,"guanyu");//2.主线程进程等待,并且打印最终的结果pthread_join(tid,NULL);pthread_join(tid2,NULL);//5.销毁互斥量pthread_mutex_destroy(&mutex);int i = 0;printf("字符指针数组中的内容是:");for(i = 0; i < pos; i++){printf("%s ",buf[i]);}printf("\n");return 0;
}
两个嵌套的互斥锁会产生死锁
2、产生条件
(1)互斥条件
(2)请求和保持条件
(3)不剥夺条件
(4)环路等待条件
3、处理方法
(1)预防死锁
这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
(2)避免死锁
该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。
(3)检测和解除死锁
先检测:这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源。检测方法包括定时检测、效率低时检测、进程等待时检测等。然后解除死锁:采取适当措施,从系统中将已发生的死锁清除掉。
这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。
四、函数 pthread_mutex_timedlock
#include <pthread.h>
#include <time.h>int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abs_timeout);
返回值:成功返回 0;失败返回错误编号
1、函数功能
2、示例说明
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include "apue.h"int main (void)
{int err;struct timespec tout;struct tm *tmp;char buf[64];pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock (&lock);printf ("mutex is locked\n");clock_gettime (CLOCK_REALTIME, &tout);tmp = localtime (&tout.tv_sec); strftime (buf, sizeof (buf), "%r", tmp);printf ("current time is %s\n", buf);tout.tv_sec += 10;err = pthread_mutex_timedlock (&lock, &tout);clock_gettime (CLOCK_REALTIME, &tout);tmp = localtime (&tout.tv_sec);strftime (buf, sizeof (buf), "%r", tmp);printf ("the time is now %s\n", buf);if (err == 0)printf ("mutex locked again\n");else printf ("can`t lock mutex again:%s\n", strerror (err));return 0;
}
编译:# gcc test.c -lpthread -lrt 输出结果:
mutex is locked
current time is 03:11:30 PM
the time is now 03:11:40 PM
can`t lock mutex again:Connection timed out
3、示例解析
五、读写锁
1、读写锁初始化和销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
两个函数返回值:若成功,返回 0;否则,返回错误编号
读写锁通过调用 pthread_rwlock_init 进行动态初始化。如果希望读写锁有默认的属性,可以传一个 NULL 指针给 attr。可以调用常量 PTHREAD_RWLOCK_INITIALIZER 进行静态初始化。
2、读写锁解锁
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.
要在读模式下锁定读写锁,需要调用 pthread_rwlock_rdlock
3、示例说明
参看:线程同步与互斥:读写锁
#include<stdio.h>
#include<unistd.h>
#include<pthread.h> pthread_rwlock_t rwlock; //读写锁
int num = 1; //读操作,其他线程允许读操作,却不允许写操作
void *fun1(void *arg)
{ while(1) { pthread_rwlock_rdlock(&rwlock); printf("read num first===%d\n",num); pthread_rwlock_unlock(&rwlock); sleep(1); }
} //读操作,其他线程允许读操作,却不允许写操作
void *fun2(void *arg)
{ while(1) { pthread_rwlock_rdlock(&rwlock); printf("read num second===%d\n",num); pthread_rwlock_unlock(&rwlock); sleep(2); }
} //写操作,其它线程都不允许读或写操作
void *fun3(void *arg)
{ while(1) { pthread_rwlock_wrlock(&rwlock); num++; printf("write thread first\n"); pthread_rwlock_unlock(&rwlock); sleep(2); }
} //写操作,其它线程都不允许读或写操作
void *fun4(void *arg)
{ while(1) { pthread_rwlock_wrlock(&rwlock); num++; printf("write thread second\n"); pthread_rwlock_unlock(&rwlock); sleep(1); }
} int main()
{ pthread_t ptd1, ptd2, ptd3, ptd4; pthread_rwlock_init(&rwlock, NULL);//初始化一个读写锁 //创建线程 pthread_create(&ptd1, NULL, fun1, NULL); pthread_create(&ptd2, NULL, fun2, NULL); pthread_create(&ptd3, NULL, fun3, NULL); pthread_create(&ptd4, NULL, fun4, NULL); //等待线程结束,回收其资源 pthread_join(ptd1,NULL); pthread_join(ptd2,NULL); pthread_join(ptd3,NULL); pthread_join(ptd4,NULL); pthread_rwlock_destroy(&rwlock);//销毁读写锁 return 0;
}
编译:# gcc test.c -lpthread输出结果:
write thread second
write thread first
read num second===3
read num first===3
write thread second
read num first===4
write thread first
read num second===5
write thread second
read num first===6
.....
4、示例解析
在此示例程序中,共创建了 4 个线程,其中两个线程用来写入数据,两个线程用来读取数据。
六、条件变量
1、条件变量概念
2、条件变量初始化和销毁
#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;否则,返回错误编号
(1)参数解析
可以用宏 PTHREAD_COND_INITIALIZER 来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init 函数动态分配的效果是一样的。初始化时不进行错误检查。
3、条件变量等待
#include <pthread.h>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);
两个函数的返回值:若陈宫,返回 0;否则,返回错误编号
(1)参数解析
一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。阻塞在条件变量上的线程被唤醒以后,直到 pthread_cond_wait() 函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用 pthread_cond_wait 函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock();
while (condition_is_false) pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
注意:pthread_cond_wait() 函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。
4、条件变量唤醒
#include <pthread.h>int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
两个函数的返回值:若成功,返回 0;否则,返回错误编号
(1)函数解析
唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是 SCHED_OTHER 类型的,系统将根据线程的优先级唤醒线程。如果没有线程被阻塞在条件变量上,那么调用 pthread_cond_signal() 将没有作用。
5、示例说明
//示例一
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> pthread_cond_t taxicond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t taximutex = PTHREAD_MUTEX_INITIALIZER; void *traveler_arrive(void *name)
{ char *p = (char *)name; printf ("Travelr: %s need a taxi now!\n", p); pthread_mutex_lock(&taximutex); pthread_cond_wait(&taxicond, &taximutex); pthread_mutex_unlock(&taximutex); printf ("traveler: %s now got a taxi!\n", p); pthread_exit(NULL);
} void *taxi_arrive(void *name)
{ char *p = (char *)name; printf ("Taxi: %s arrives.\n", p); pthread_cond_signal(&taxicond); pthread_exit(NULL);
} int main (int argc, char **argv)
{ char *name; pthread_t thread; pthread_attr_t threadattr; pthread_attr_init(&threadattr); name = "Jack"; pthread_create(&thread, &threadattr, taxi_arrive, name); sleep(1); name = "Susan"; pthread_create(&thread, &threadattr, traveler_arrive, name); sleep(1); name = "Mike"; pthread_create(&thread, &threadattr, taxi_arrive, name); sleep(1); return 0;
}
编译:# gcc test.c -lpthread输出结果:
Taxi: Jack arrives.
Travelr: Susan need a taxi now!
Taxi: Mike arrives.
traveler: Susan now got a taxi!
//示例二
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> int travelercount = 0;
pthread_cond_t taxicond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t taximutex = PTHREAD_MUTEX_INITIALIZER; void *traveler_arrive(void *name)
{ char *p = (char *)name; pthread_mutex_lock(&taximutex); printf ("traveler: %s need a taxi now!\n", p); travelercount++; pthread_cond_wait(&taxicond, &taximutex); pthread_mutex_unlock(&taximutex); printf ("traveler: %s now got a taxi!\n", p); pthread_exit(NULL);
} void *taxi_arrive(void *name)
{ char *p = (char *)name; printf ("Taxi: %s arrives.\n", p); for(;;){ if(travelercount){ pthread_cond_signal(&taxicond); travelercount--; break; } } pthread_exit(NULL);
} int main (int argc, char **argv)
{ char *name; pthread_t thread; pthread_attr_t threadattr; pthread_attr_init(&threadattr); name = "Jack"; pthread_create(&thread, &threadattr, taxi_arrive, name); sleep(1); name = "Susan"; pthread_create(&thread, &threadattr, traveler_arrive, name); sleep(3); name = "Mike"; pthread_create(&thread, &threadattr, taxi_arrive, name); sleep(4); return 0;
}
编译:# gcc test.c -lpthread输出结果:
Taxi: Jack arrives.
traveler: Susan need a taxi now!
traveler: Susan now got a taxi!
Taxi: Mike arrives.
七、自旋锁
自旋锁在用户态使用的比较少,在内核使用的比较多!自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间。
自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_xxx()中mutex换成spin,如:pthread_spin_init()。
八、总结
UNIX再学习 -- 线程同步相关推荐
- UNIX再学习 -- 线程
终于要讲到线程部分,线程和进程让人够头痛的内容. 一.线程概念 老样子,我们还是按我们讲进程时的方式说起,参看:UNIX再学习 -- 进程环境 首先需要了解下,什么是线程. Linux 下的线程,可能 ...
- UNIX再学习 -- 线程控制
留楼以后有时间再讲. 感慨一下,线程部分有点懵逼.线程同步除了互斥量,好像其他的都不熟悉,没怎么用过. 搞的我没有心情看一下去了.APUE 第 12 章先跳过去,抓紧看更重要的东西吧.
- UNIX再学习 -- exit 和 wait 系列函数
我们一开始讲进程环境时,就有提到了.进程有 8 种方式使进程终止. 其中 5 种为正常终止,它们是: (1)在 main 函数中执行 return (2)调用 exit 函数,并不处理文件描述符,多进 ...
- UNIX再学习 -- 记录锁
APUE第 3 章,参看:UNIX再学习 -- 文件I/O fcntl 函数它的记录锁功能我们当时没讲.接下来就详细说明下. 一.读写冲突 1.如果两个或两个以上的进程同时向一个文件的某个特定的区域 ...
- UNIX再学习 -- 文件描述符
在 UNIX/Linux 系统中,一切皆文件,这句话想必都有听过.对于文件的操作几乎适用于所有的设备,这也就看出了文件操作的重要性了.在C语言再学习部分有讲过标准I/O文件操作,参看:C语言再学习 - ...
- UNIX再学习 -- 进程间通信之管道
一.进程间通信概念 首先,需要了解一下什么是进程间通信. 进程之间的相互通信的技术,称为进程间通信(InterProcess Communication,IPC). 下图列出 4 种实现所支持的不同形 ...
- UNIX再学习 -- 守护进程(转)
参看:守护进程 一.什么是守护进程 守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程.它是一个生存期较长的进程,通常独立于控制 ...
- UNIX再学习 -- 进程关系
APUE 第 10 章信号讲完,回过头来看一下第 9 章的进程关系.终端登录和网络登录部分,我们只讲 Linux 系统的. 一.终端登录 我记得我们讲 root 登录设置时有提到,参看:C语言再学习 ...
- UNIX再学习 -- 函数abort
abort 函数之前有讲过的,参看:C语言再学习 -- 关键字return和exit ()函数 然后我们在讲 8 中进程终止时,也说过.参看:UNIX再学习 -- exit 和 wait 系列函数 下 ...
最新文章
- r语言和python-r语言和python学哪个?
- tableau和powerbi的联系和区别
- [C++调试笔记]初始化3种粒子数据initmaxw
- MSF(六):后渗透
- hiveserver或者hive启动出现Expected authority at index 7问题解决
- python PIL图像处理-框选
- 最全、最详细的配置jdk十步法!
- postgres的序列(Sequence)的使用
- OpenCV---模板匹配
- ffmpeg aac解码pcm
- 用python实现传染病模型传染病模型
- python_体脂率的计算
- 关于建站、服务器、云虚拟主机你想知道的都在这里!
- 又一微信自动化框架wxauto横空出世了!
- 市场调研报告-全球与中国教育互动白板市场现状及未来发展趋势
- 海康威视监控摄像头大华摄像头webrtc监控低时延无插件直播页面播放毫无延迟
- css+html 在文字下面加点。
- 关键词推广怎么做比较好?抖音宣传做关键词推广有哪些好的方法
- P4113 [HEOI2012]采花 ( 树状数组 + 离线 )
- 四种电子取证软件的比较