热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

多线程之同步异步

1.线程异步线程在创建的之后,一般都是独立自主,并发的,线程间会进行资源的竞争,那么就会引来一个问题,如果多个线程在同一时间对同一资源进行访问,修改,会造成资源破坏的结果,如下

1. 线程异步

  线程在创建的之后,一般都是独立自主,并发的,线程间会进行资源的竞争,那么就会引来一个问题,如果多个线程在同一时间对同一资源进行访问,修改,会造成资源破坏的结果,如下例子:

#include
#include


#include

int g_num = 0;
void *pth_fun1(void *arg)
{
while(1) {
printf(
"%d\n", g_num++);
}
}
void *pth_fun2(void *arg)
{
while(1) {
printf(
"%d\n", g_num++);
}
}
int main(void)
{
pthread_t pth1, pth2;
int res = pthread_create(&pth1, NULL, pth_fun1, NULL);
if (res) {
printf(
"create pthread error!\n");
return 0;
}
res
= pthread_create(&pth2, NULL, pth_fun2, NULL);
if (res) {
printf(
"create pthread error!\n");
return 0;
}
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
printf(
"hello world!\n");
return 0;
}

运行结果如下:

kunmzhao@build-245:~$ gcc 1.c -o main -lpthread
kunmzhao@build-245:~$ clear
kunmzhao@build-245:~$ gcc 1.c -o main -lpthread
kunmzhao@build-245:~$ ./main
0
2
3
4
5
1
7
8
9

^C

这个例子中,两个线程分别打印一个全局变量并且加1,理想是从0开始,每次加1, 结果却事与愿违原因就在于异步带来的问题,所以我们希望当一个线程在操作某个可能被其他线程访问的资源时,只允许该线程操作,只有该线程结束操作资源时,其它线程才可以访问,这也就是线程同步

2. 线程同步

  并发和异步带来了线程间资源的竞争的无序性, 因此需要同步机制来消除这种缺陷,实现线程正确有序共享数据,我们常用的方法就是锁,如互斥锁,读写锁和条件变量

3. 互斥锁

  互斥锁可以保护多线程的共享资源,同一时刻只允许一个线程对临界区进行访问

  互斥锁的使用流程如下:

  1) . 初始化一个互斥锁

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)







      • mutex:一个互斥锁指针

      • attr:设置该互斥锁的属性,通常用NULL

      • 返回值:成功:0, 失败:errno





  2). 线程进入临界区前进行加锁

    int pthread_mutex_lock(pthread_mutex_t  *mutex)







      • mutex: 已初始化过的互斥锁

      • 返回值: 成功:0, 失败:errno





  3). 线程退出临界区前进行解锁

    int pthread_mutex_unlock(pthread_mutex_t *mutex)







      • mutex:已经上过锁的互斥锁

      • 返回值:成功:0, 失败:errno





  4). 在最后不用互斥锁时销毁

    int pthread_mutex_destory(pthread_mutex_t *mutex)







      • mutex:已经初始化过的互斥锁

      • 返回值:成功:0, 失败:errno





 

#include
#include


#include

int g_a = 100;
int g_b = 200;
pthread_mutex_t mutex;
void *pth_fun1(void *arg)
{
while(1) {
pthread_mutex_lock(
&mutex); // 上锁
g_a -= 5;
g_b
+= 5;
pthread_mutex_unlock(
&mutex); // 解锁
}
}
void *pth_fun2(void *arg)
{
while(1) {
pthread_mutex_lock(
&mutex); // 上锁
printf("sum = %d\n", g_a + g_b);
pthread_mutex_unlock(
&mutex); // 解锁
sleep(1);
}
}
int main(void)
{

// 初始化一个互斥锁
int res = pthread_mutex_init(&mutex, NULL);
if (res) {
printf(
"create mutex error!\n");
return 0;
}
// 创建线程
pthread_t pth1, pth2;
res
= pthread_create(&pth1, NULL, pth_fun1, NULL);
if (res) {
printf(
"create pthread error!\n");
return 0;
}
res
= pthread_create(&pth2, NULL, pth_fun2, NULL);
if (res) {
printf(
"create pthread error!\n");
return 0;
}
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
// 销毁线程
pthread_mutex_destroy(&mutex);
printf(
"hello world!\n");
return 0;
}

 

运行结果如下:

 

kunmzhao@build-245:~$ gcc 1.c -o main -lpthread
kunmzhao@build-245:~$ ./main
sum = 300
sum = 300
sum = 300
sum = 300
sum = 300
sum = 300
sum = 300
sum = 300
^C
kunmzhao@build-245:~$

 

4. 读写锁

  从上面我们可以看出,互斥锁只有两种状态,一个是上锁了,另一个就是没上锁,我们现在假设有10个线程,都要在同一时间访问某一个变量,每个线程要占用资源1秒,那么第十个线程就要等待10秒,这在多线程中是很不友好的。其实对某一个资源而言,只是访问该资源,而不是修改的时候,多个线程同时访问是不会破坏该资源的,因此我们引入读写锁。

 读写锁分为读锁和写锁,一个线程在读取资源的时候,我们上读锁,而另一个线程也来读取资源的时候,发现上了读锁,但是仍然可以进入临界区,访问该资源。线程需要修改资源的时候需要上写锁,一旦某个线程上了写锁,他就跟互斥锁一样了,只能等待该线程解锁,其他线程的读锁和写锁才能再次访问   

  读写锁的使用:

    1). 初始化读写锁

      int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)

      rwlock:一个读写锁

      attr:设置读写锁参数,常为NULL

      返回值:成功:0, 失败:errno

    2). 上读锁 

      int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)

      rwlock:已经初始化的读写锁

      返回值:成功0,失败:errno

    3) 上写锁

      int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)

      rwlock:已经初始化的读写锁

      返回值:成功0,失败:errno

    4)  解锁

      int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)

      rwlock:已经上锁的读写锁

      返回值:成功0,失败:errno

    5)销毁读写锁

      int pthread_rwlock_destroy(pthread_rwlock_t * rwlock)

      rwlock:已经初始化的互斥锁

      返回值:成功0,失败:errno

 

  下面用一个demon来展示读写锁和互斥锁的运行速度比拼

#include
#include


#include

int g_a = 100;
// 分别定义一个互斥锁和读写锁
pthread_mutex_t g_mutex;
pthread_rwlock_t g_rwlock;
// 线程1
void *pth_fun1(void *arg)
{
for(int i = 0; i <10000000; i++) {
pthread_mutex_lock(
&g_mutex); // 上锁
int temp = g_a; // 只读全局变量
pthread_mutex_unlock(&g_mutex); // 解锁
}
pthread_exit(
0);
}
// 线程2
void *pth_fun2(void *arg)
{
for(int i = 0; i <10000000; i++) {
pthread_mutex_lock(
&g_mutex); // 上锁
int temp = g_a; // 只读全局变量
pthread_mutex_unlock(&g_mutex); // 解锁
}
pthread_exit(
0);
}
// 线程3
void *pth_fun3(void *arg)
{
for(int i = 0; i <10000000; i++) {
pthread_rwlock_rdlock(
&g_rwlock); // 上锁
int temp = g_a; // 只读全局变量
pthread_rwlock_unlock(&g_rwlock); // 解锁
}
pthread_exit(
0);
}
// 线程4
void *pth_fun4(void *arg)
{
for(int i = 0; i <10000000; i++) {
pthread_rwlock_rdlock(
&g_rwlock); // 上锁
int temp = g_a; // 只读全局变量
pthread_rwlock_unlock(&g_rwlock); // 解锁
}
pthread_exit(
0);
}
// 互斥锁函数
void mutex_fun()
{
pthread_t pth1,pth2;
struct timeval start, end;
pthread_mutex_init(
&g_mutex, NULL); // 初始化一个互斥锁
gettimeofday(&start, NULL);
int res = pthread_create(&pth1, NULL, pth_fun1, NULL);
if(res) {
printf(
"create pthread error!\n");
return ;
}
res
= pthread_create(&pth2, NULL, pth_fun2, NULL);
if(res) {
printf(
"create pthread error!\n");
return ;
}
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
gettimeofday(
&end, NULL);
long long total = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec);// 微秒
long total_msec = total / 1000; // 毫秒
printf("mutex takes %ld msec\n", total_msec);
pthread_mutex_destroy(
&g_mutex);
return;
}
// 读写锁函数
void rwlock_fun()
{
pthread_t pth1,pth2;
struct timeval start, end;
pthread_rwlock_init(
&g_rwlock, NULL);
gettimeofday(
&start, NULL);
int res = pthread_create(&pth1, NULL, pth_fun3, NULL);
if(res) {
printf(
"create pthread error!\n");
return ;
}
res
= pthread_create(&pth2, NULL, pth_fun4, NULL);
if(res) {
printf(
"create pthread error!\n");
return ;
}
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
gettimeofday(
&end, NULL);
long long total = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec);// 微秒
long total_msec = total / 1000; // 毫秒
printf("rwlock takes %ld msec\n", total_msec);
pthread_rwlock_destroy(
&g_rwlock);
return;
}
int main(void)
{
mutex_fun();
rwlock_fun();
printf(
"hello world!\n");
return 0;
}

运行结果如下:

kunmzhao@build-245:~$ ./main
mutex takes 1608 msec
rwlock takes 2679 msec
hello world!
kunmzhao@build-245:~$ ./main
mutex takes 1753 msec
rwlock takes 2039 msec
hello world!
kunmzhao@build-245:~$ ./main
mutex takes 1433 msec
rwlock takes 2877 msec
hello world!
kunmzhao@build-245:~$ ./main
mutex takes 1646 msec
rwlock takes 2275 msec
hello world!
kunmzhao@build-245:~$ ./main
mutex takes 1607 msec
rwlock takes 2124 msec
hello world!
kunmzhao@build-245:~$ ./main
mutex takes 1350 msec
rwlock takes 2001 msec
hello world!
kunmzhao@build-245:~$ ./main
mutex takes 1591 msec
rwlock takes 2448 msec
hello world!
kunmzhao@build-245:~$ ./main
mutex takes 1370 msec
rwlock takes 2973 msec
hello world!
kunmzhao@build-245:~$

 

5. 条件变量

   当我们希望一个线程在某个条件下才能执行,比如一个缓存区,其中一个线程不断读取,一个不断写入,当缓存区满了,则写线程就应该暂停,同理,缓存区空了,读线程就应该停止,我们引入条件变量来同步线程

   条件变量使用步骤:

   1)初始化一个条件变量

    int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr)

    cond:一个条件变量

    attr:设置条件变量,常设置为NULL

    返回值:成功:0, 失败:errno

   2) 等待条件变量

    int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex)

    cond:已初始化的条件变量

    mutex:已上锁的互斥锁

    返回值:成功:0, 失败:errno

   3) 唤醒等待变量

    int pthread_cond_signal(pthread_cond_t *cond)

    cond:已经初始化的条件变量

    返回值:成功:0, 失败:errno

  注意点:

  1. 条件变量必须和互斥锁配合使用

  2. pthread_cond_wait()调用的时候必须已经上锁,因为第二个形参就是该互斥锁,因为该函数会解锁,同时条件变量会作为阻塞,等待唤醒的到来,醒来后会再次加锁

  3. pthread_cond_signal()调用的时候,最后后面就是要解锁

  

#include
#include


char g_buff[100] = {0};
int g_buff_size = 0;
pthread_mutex_t g_mutex;
pthread_cond_t g_cond;
// put char in g_buff
void *pth_put(void *arg)
{
for(int i = 0; i <1000000; i++) {
if (g_buff == NULL)
return NULL;
pthread_mutex_lock(
&g_mutex);
if (g_buff_size == 100) {
printf(
"put:g_buff is full!\n");
pthread_cond_wait(
&g_cond, &g_mutex);
//printf("put :pthread received signal\n");
}
g_buff[g_buff_size]
= 'A';
g_buff_size
++;
if (g_buff_size == 1) {
//printf("put:g_buff is not empty!\n");
pthread_cond_signal(&g_cond);
}
pthread_mutex_unlock(
&g_mutex);
}
}
// get char from g_buff
void *pth_get(void *arg)
{
for(int i = 0; i <1000000; i++) {
if(g_buff == NULL)
return NULL;
pthread_mutex_lock(
&g_mutex);
if(g_buff_size == 0) {
printf(
"get:g_buff is empty!\n");
pthread_cond_wait(
&g_cond, &g_mutex);
//printf("get :pthread received signal\n");
}
g_buff[g_buff_size
- 1] = 0;
g_buff_size
--;
if (g_buff_size == 00) {
//printf("get:g_buff is not full!\n");
pthread_cond_signal(&g_cond);
}
pthread_mutex_unlock(
&g_mutex);
}
}
int main(void)
{
pthread_t pth1, pth2;
pthread_mutex_init(
&g_mutex, NULL);
pthread_cond_init(
&g_cond, NULL);
pthread_create(
&pth1, NULL, pth_get, NULL);
pthread_create(
&pth2, NULL, pth_put, NULL);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
pthread_mutex_destroy(
&g_mutex);
pthread_cond_destroy(
&g_cond);
for (int i = 0; i <100; i++)
{
printf(
"%d ", g_buff[i]);
}
return 0;
}

 

   

      

   



推荐阅读
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了在Oracle数据库中创建序列时如何选择cache或nocache参数。cache参数可以提高序列的存取速度,但可能会导致序列丢失;nocache参数可以避免序列丢失,但在高并发访问时可能导致性能问题。文章详细解释了两者的区别和使用场景。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • OO第一单元自白:简单多项式导函数的设计与bug分析
    本文介绍了作者在学习OO的第一次作业中所遇到的问题及其解决方案。作者通过建立Multinomial和Monomial两个类来实现多项式和单项式,并通过append方法将单项式组合为多项式,并在此过程中合并同类项。作者还介绍了单项式和多项式的求导方法,并解释了如何利用正则表达式提取各个单项式并进行求导。同时,作者还对自己在输入合法性判断上的不足进行了bug分析,指出了自己在处理指数情况时出现的问题,并总结了被hack的原因。 ... [详细]
  • 本文总结了淘淘商城项目的功能和架构,并介绍了传统架构中遇到的session共享问题及解决方法。淘淘商城是一个综合性的B2C平台,类似京东商城、天猫商城,会员可以在商城浏览商品、下订单,管理员、运营可以在平台后台管理系统中管理商品、订单、会员等。商城的架构包括后台管理系统、前台系统、会员系统、订单系统、搜索系统和单点登录系统。在传统架构中,可以采用tomcat集群解决并发量高的问题,但由于session共享的限制,集群数量有限。本文探讨了如何解决session共享的问题。 ... [详细]
  • 关于CMS收集器的知识介绍和优缺点分析
    本文介绍了CMS收集器的概念、运行过程和优缺点,并解释了垃圾回收器的作用和实践。CMS收集器是一种基于标记-清除算法的垃圾回收器,适用于互联网站和B/S系统等对响应速度和停顿时间有较高要求的应用。同时,还提供了其他垃圾回收器的参考资料。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文介绍了Redis中RDB文件和AOF文件的保存和还原机制。RDB文件用于保存和还原Redis服务器所有数据库中的键值对数据,SAVE命令和BGSAVE命令分别用于阻塞服务器和由子进程执行保存操作。同时执行SAVE命令和BGSAVE命令,以及同时执行两个BGSAVE命令都会产生竞争条件。服务器会保存所有用save选项设置的保存条件,当满足任意一个保存条件时,服务器会自动执行BGSAVE命令。此外,还介绍了RDB文件和AOF文件在操作方面的冲突以及同时执行大量磁盘写入操作的不良影响。 ... [详细]
  • 如何使用代理服务器进行网页抓取?
    本文介绍了如何使用代理服务器进行网页抓取,并探讨了数据驱动对竞争优势的重要性。通过网页抓取,企业可以快速获取并分析大量与需求相关的数据,从而制定营销战略。同时,网页抓取还可以帮助电子商务公司在竞争对手的网站上下载数百页的有用数据,提高销售增长和毛利率。 ... [详细]
author-avatar
裸身耍丶暧昧800
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有