0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

多线程不进行同步会造成什么问题

科技绿洲 来源:Linux开发架构之路 作者:Linux开发架构之路 2023-11-13 11:40 次阅读

背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?

通过多线程模拟多窗口售票为例:

#include

#include

#include

#include

#include

#include

using namespace std;

int ticket_sum=20;

void *sell_ticket(void *arg)

{

for(int i=0; i<20; i++)

{

if(ticket_sum>0)

{

sleep(1);

cout<<"sell the "<<20-ticket_sum+1<<"th"<

ticket_sum--;

}

}

return 0;

}

int main()

{

int flag;

pthread_t tids[4];

for(int i=0; i<4; i++)

{

flag=pthread_create(&tids[i],NULL,&sell_ticket,NULL);

if(flag)

{

cout<<"pthread create error ,flag="<

return flag;

}

}

sleep(20);

void *ans;

for(int i=0; i<4; i++)

{

flag=pthread_join(tids[i],&ans);

if(flag)

{

cout<<"tid="<

分析:总票数只有20张,却卖出了23张,是非常明显的超买超卖问题,而造成这个问题的根本原因就是同时发生的各个线程都可以对ticket_sum进行读取和写入!

ps:

1.在并发情况下,指令执行的先后顺序由内核决定,同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清楚是哪一个先执行,如果运行的结果依赖于不同线程执行的先后的话,那么就会形成竞争条件,在这样的情况下,计算的结果很难预知,所以应该尽量避免竞争条件的形成

2.最常见的解决竞争条件的方法是将原先分离的两个指令构成一个不可分割的原子操作,而其他任务不能插入到原子操作中!

3.对多线程来说,同步指的是在一定时间内只允许某一个线程访问某个资源,而在此时间内,不允许其他线程访问该资源!

4.线程同步的常见方法:互斥锁,条件变量,读写锁,信号

一.互斥锁

本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,一旦获得,这个互斥锁会锁上变成lock状态,此后只有该线程由权力打开该锁,其他线程想要获得互斥锁,必须得到互斥锁再次被打开之后

采用互斥锁来同步资源:

#include

#include

#include

#include

#include

#include

using namespace std;

int ticket_sum=20;

pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//static init mutex

void *sell_ticket(void *arg)

{

for(int i=0; i<20; i++)

{

pthread_mutex_lock(&mutex_x);//atomic opreation through mutex lock

if(ticket_sum>0)

{

sleep(1);

cout<<"sell the "<<20-ticket_sum+1<<"th"<

ticket_sum--;

}

pthread_mutex_unlock(&mutex_x);

}

return 0;

}

int main()

{

int flag;

pthread_t tids[4];

for(int i=0; i<4; i++)

{

flag=pthread_create(&tids[i],NULL,&sell_ticket,NULL);

if(flag)

{

cout<<"pthread create error ,flag="<

return flag;

}

}

sleep(20);

void *ans;

for(int i=0; i<4; i++)

{

flag=pthread_join(tids[i],&ans);

if(flag)

{

cout<<"tid="<

ticket_sum--;

}

sleep(1);

pthread_mutex_unlock(&mutex_x);

sleep(1);

}

return 0;

}

void *sell_ticket_2(void *arg)

{

int flag;

for(int i=0; i<10; i++)

{

flag=pthread_mutex_trylock(&mutex_x);

if(flag==EBUSY)

{

cout<<"sell_ticket_2:the variable is locked by sell_ticket_1"<

}

else if(flag==0)

{

if(ticket_sum>0)

{

sleep(1);

cout<<"thread_2 sell the "<<20-ticket_sum+1<<"th tickets"<

ticket_sum--;

}

pthread_mutex_unlock(&mutex_x);

}

sleep(1);

}

return 0;

}

int main()

{

int flag;

pthread_t tids[2];

flag=pthread_create(&tids[0],NULL,&sell_ticket_1,NULL);

if(flag)

{

cout<<"pthread create error ,flag="<

return flag;

}

flag=pthread_create(&tids[1],NULL,&sell_ticket_2,NULL);

if(flag)

{

cout<<"pthread create error ,flag="<

return flag;

}

void *ans;

sleep(30);

flag=pthread_join(tids[0],&ans);

if(flag)

{

cout<<"tid="<

分析:通过测试加锁函数我们可以清晰的看到两个线程争用资源的情况

二.条件变量

互斥量不是万能的,比如某个线程正在等待共享数据内某个条件出现,可可能需要重复对数据对象加锁和解锁(轮询),但是这样轮询非常耗费时间和资源,而且效率非常低,所以互斥锁不太适合这种情况

我们需要这样一种方法:当线程在等待满足某些条件时使线程进入睡眠状态,一旦条件满足,就换线因等待满足特定条件而睡眠的线程

如果我们能够实现这样一种方法,程序的效率无疑会大大提高,而这种方法正是条件变量!

样例:

#include

#include

#include

#include

#include

#include

#include

using namespace std;

pthread_cond_t qready=PTHREAD_COND_INITIALIZER; //cond

pthread_mutex_t qlock=PTHREAD_MUTEX_INITIALIZER; //mutex

int x=10,y=20;

void *f1(void *arg)

{

cout<<"f1 start"<

pthread_mutex_lock(&qlock);

while(x

{

pthread_cond_wait(&qready,&qlock);

}

pthread_mutex_unlock(&qlock);

sleep(3);

cout<<"f1 end"<

return 0;

}

void *f2(void *arg)

{

cout<<"f2 start"<

pthread_mutex_lock(&qlock);

x=20;

y=10;

cout<<"has a change,x="<

return 0;

}

int main()

{

pthread_t tids[2];

int flag;

flag=pthread_create(&tids[0],NULL,f1,NULL);

if(flag)

{

cout<<"pthread 1 create error "<

return flag;

}

sleep(2);

flag=pthread_create(&tids[1],NULL,f2,NULL);

if(flag)

{

cout<<"pthread 2 create erro "<

return flag;

}

sleep(5);

return 0;

}

图片

分析:线程1不满足条件被阻塞,然后线程2运行,改变了条件,线程2发行条件改变了通知线程1运行,然线程1不满足条件被阻塞,然后线程2运行,改变了条件,线程2发行条件改变了通知线程1运行,然后线程2结束,然后线程1继续运行,然后线程1结束,为了确保线程1先执行,在创建线程2之前我们sleep了2秒

ps:

1.条件变量通过运行线程阻塞和等待另一个线程发送信号的方法弥补互斥锁的不足,常常和互斥锁一起使用,使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开响应的互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知响应的条件变量换线一个或多个正被此条件变量阻塞的线程,这些线程将重新锁定互斥锁并且重新测试条件是否满足

1.条件变量的相关函数

1)创建

静态方式:pthread_cond_t cond PTHREAD_COND_INITIALIZER

动态方式:int pthread_cond_init(&cond,NULL)

Linux thread 实现的条件变量不支持属性,所以NULL(cond_attr参数

2)注销

int pthread_cond_destory(&cond)

只有没有线程在该条件变量上,该条件变量才能注销,否则返回EBUSY

因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程!(请参考条件变量的底层实现)

3)等待

条件等待:int pthread_cond_wait(&cond,&mutex)

计时等待:int pthread_cond_timewait(&cond,&mutex,time)

1.其中计时等待如果在给定时刻前条件没有被满足,则返回ETIMEOUT,结束等待

2.无论那种等待方式,都必须有一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait形成竞争条件!

3.在调用pthread_cond_wait前必须由本线程加锁

4)激发

激发一个等待线程:pthread_cond_signal(&cond)

激发所有等待线程:pthread_cond_broadcast(&cond)

重要的是,pthread_cond_signal不会存在惊群效应,也就是是它最多给一个等待线程发信号,不会给所有线程发信号唤醒提他们,然后要求他们自己去争抢资源!

pthread_cond_signal会根据等待线程的优先级和等待时间来确定激发哪一个等待线程

下面看一个程序,找到程序存在的问题

#include

#include

#include

#include

#include

#include

#include

using namespace std;

pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //taix arrive cond

pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER;// sync mutex

void *traveler_arrive(void *name)

{

cout<<"Traveler:"<<(char*)name<<" needs a taxi now!"<

pthread_mutex_lock(&taxi_mutex);

pthread_cond_wait(&taxi_cond,&taxi_mutex);

pthread_mutex_unlock(&taxi_mutex);

cout<<"Traveler:"<<(char*)name<<" now got a taxi!"<

pthread_exit((void*)0);

}

void *taxi_arrive(void *name)

{

cout<<"Taxi:"<<(char*)name<<" arriver."<

pthread_cond_signal(&taxi_cond);

pthread_exit((void*)0);

}

int main()

{

pthread_t tids[3];

int flag;

flag=pthread_create(&tids[0],NULL,taxi_arrive,(void*)("Jack"));

if(flag)

{

cout<<"pthread_create error:flag="<

return flag;

}

cout<<"time passing by"<

sleep(1);

flag=pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));

if(flag)

{

cout<<"pthread_create error:flag="<

return flag;

}

cout<<"time passing by"<

sleep(1);

flag=pthread_create(&tids[2],NULL,taxi_arrive,(void*)("Mike"));

if(flag)

{

cout<<"pthread_create error:flag="<

return flag;

}

cout<<"time passing by"<

sleep(1);

void *ans;

for(int i=0; i<3; i++)

{

flag=pthread_join(tids[i],&ans);

if(flag)

{

cout<<"pthread_join error:flag="<

return flag;

}

cout<<"ans="<

}

return 0;

}

图片

分析:程序由一个条件变量,用于提示乘客有出租车到达,还有一个同步锁,乘客到达之后就是等车(条件变量),出租车到达之后就是通知乘客,我们看到乘客Susan到达之后,并没有乘坐先到的Jack的车,而是等到Mike的车到了之后再乘坐Mike的车,Jack的车白白的闲置了,为什么会造成这种原因呢?分析一下代码:我们发现Jack出租车到达之后调用pthread_cond_signal(&taxi_cond)发现没有乘客,然后就直接结束线程了。。。。

正确的操作应该是:先到的Jack发现没有乘客,然后一直等待乘客,有乘客到了就直接走,而且我们应该统计一下乘客的数量

做如下改进:

1.增加乘客计数器,使得出租车在有乘客到达之后可以直接走,而不是又在原地等待别的乘客(僵死线程)

2.出租车到达函数加个while循环,没有乘客的时候一直等待,直到乘客到来

#include

#include

#include

#include

#include

#include

#include

using namespace std;

pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //taix arrive cond

pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER;// sync mutex

void *traveler_arrive(void *name)

{

cout<<"Traveler:"<<(char*)name<<" needs a taxi now!"<

pthread_mutex_lock(&taxi_mutex);

pthread_cond_wait(&taxi_cond,&taxi_mutex);

pthread_mutex_unlock(&taxi_mutex);

cout<<"Traveler:"<<(char*)name<<" now got a taxi!"<

pthread_exit((void*)0);

}

void *taxi_arrive(void *name)

{

cout<<"Taxi:"<<(char*)name<<" arriver."<

pthread_exit((void*)0);

}

int main()

{

pthread_t tids[3];

int flag;

flag=pthread_create(&tids[0],NULL,taxi_arrive,(void*)("Jack"));

if(flag)

{

cout<<"pthread_create error:flag="<

return flag;

}

cout<<"time passing by"<

sleep(1);

flag=pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));

if(flag)

{

cout<<"pthread_create error:flag="<

return flag;

}

cout<<"time passing by"<

sleep(1);

flag=pthread_create(&tids[2],NULL,taxi_arrive,(void*)("Mike"));

if(flag)

{

cout<<"pthread_create error:flag="<

return flag;

}

cout<<"time passing by"<

sleep(1);

void *ans;

for(int i=0; i<3; i++)

{

flag=pthread_join(tids[i],&ans);

if(flag)

{

cout<<"pthread_join error:flag="<

return flag;

}

cout<<"ans="<

}

return 0;

}

三.读写锁

可以多个线程同时读,但是不能多个线程同时写

1.读写锁比互斥锁更加具有适用性和并行性

2.读写锁最适用于对数据结构的读操作读操作次数多余写操作次数的场合!

3.锁处于读模式时可以线程共享,而锁处于写模式时只能独占,所以读写锁又叫做共享-独占锁

4.读写锁有两种策略:强读同步和强写同步

在强读同步中,总是给读者更高的优先权,只要写者没有进行写操作,读者就可以获得访问权限

在强写同步中,总是给写者更高的优先权,读者只能等到所有正在等待或者执行的写者完成后才能进行读

不同的系统采用不同的策略,比如航班订票系统使用强写同步,图书馆查阅系统采用强读同步

根据不同的业务场景,采用不同的策略

1)初始化的销毁读写锁

静态初始化:pthread_rwlock_t rwlock=
PTHREAD_RWLOCK_INITIALIZER

动态初始化:int pthread_rwlock_init(rwlock,NULL),NULL代表读写锁采用默认属性

销毁读写锁:int pthread_rwlock_destory(rwlock)

在释放某个读写锁的资源之前,需要先通过pthread_rwlock_destory函数对读写锁进行清理。释放由pthread_rwlock_init函数分配的资源

如果你想要读写锁使用非默认属性,则attr不能为NULL,得给attr赋值

int pthread_rwlockattr_init(attr),给attr初始化

int
pthread_rwlockattr_destory(attr),销毁attr

2)以写的方式获取锁,以读的方式获取锁,释放读写锁

int pthread_rwlock_rdlock(rwlock),以读的方式获取锁

int pthread_rwlock_wrlock(rwlock),以写的方式获取锁

int pthread_rwlock_unlock(rwlock),释放锁

上面两个获取锁的方式都是阻塞的函数,也就是说获取不到锁的话,调用线程不是立即返回,而是阻塞执行,在需要进行写操作的时候,这种阻塞式获取锁的方式是非常不好的,你想一下,我需要进行写操作,不但没有获取到锁,我还一直在这里等待,大大拖累效率

所以我们应该采用非阻塞的方式获取锁:

int pthread_rwlock_tryrdlock(rwlock)

int pthread_rwlock_trywrlock(rwlock)

读写锁的样例:

#include

#include

#include

#include

#include

#include

#include

using namespace std;

int num=5;

pthread_rwlock_t rwlock;

void *reader(void *arg)

{

pthread_rwlock_rdlock(&rwlock);

cout<<"reader "<<(long)arg<<" got the lock"<

pthread_rwlock_unlock(&rwlock);

return 0;

}

void *writer(void *arg)

{

pthread_rwlock_wrlock(&rwlock);

cout<<"writer "<<(long)arg<<" got the lock"<

pthread_rwlock_unlock(&rwlock);

return 0;

}

int main()

{

int flag;

long n=1,m=1;

pthread_t wid,rid;

pthread_attr_t attr;

flag=pthread_rwlock_init(&rwlock,NULL);

if(flag)

{

cout<<"rwlock init error"<

return flag;

}

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//thread sepatate

for(int i=0;i

{

if(i%3)

{

pthread_create(&rid,&attr,reader,(void *)n);

cout<<"create reader "<

n++;

}else

{

pthread_create(&wid,&attr,writer,(void *)m);

cout<<"create writer "<

m++;

}

}

sleep(5);//wait other done

return 0;

}

图片

分析:3个读线程,2个写线程,读线程比写线程多

当读写锁是写状态时,在锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞

当读写锁是读状态时,在锁被解锁之前,所有视图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行加锁的线程会被阻塞

所以读写锁默认是强读模式!

四.信号量

信号量(sem)和互斥锁的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程进入临界区

1)信号量初始化

int sem_init(&sem,pshared,v)

pshared为0表示这个信号量是当前进程的局部信号量

pshared为1表示这个信号量可以在多个进程之间共享

v为信号量的初始值

成功返回0,失败返回-1

2)信号量值的加减

int sem_wait(&sem):以原子操作的方式将信号量的值减去1

int sem_post(&sem):以原子操作的方式将信号量的值加上1

3)对信号量进行清理

int sem_destory(&sem)

通过信号量模拟2个窗口,10个客人进行服务的过程

样例:

#include

#include

#include

#include

#include

#include

#include

#include

using namespace std;

int num=10;

sem_t sem;

void *get_service(void *cid)

{

int id= ((int )cid);

if(sem_wait(&sem)==0)

{

sleep(5);

cout<<"customer "<

cout<<"customer "<

sem_post(&sem);

}

return 0;

}

int main()

{

sem_init(&sem,0,2);

pthread_t customer[num];

int flag;

for(int i=0;i

{

int id=i;

flag=pthread_create(&customer[i],NULL,get_service,&id);

if(flag)

{

cout<<"pthread create error"<

return flag;

}else

{

cout<<"customer "<

}

sleep(1);

}

//wait all thread done

for(int j=0;j

{

pthread_join(customer[j],NULL);

}

sem_destroy(&sem);

return 0;

}

图片

分析:信号量的值代表空闲的服务窗口,每个窗口一次只能服务一个人,有空闲窗口,开始服务前,信号量-1,服务完成后信号量+1

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1309

    浏览量

    39846
  • 窗口
    +关注

    关注

    0

    文章

    62

    浏览量

    10728
  • 多线程
    +关注

    关注

    0

    文章

    271

    浏览量

    19724
  • Lock
    +关注

    关注

    0

    文章

    10

    浏览量

    7680
收藏 人收藏

    评论

    相关推荐

    多线程编程之四 线程同步

    多线程编程之四 线程同步八、线程同步  虽然多线程能给我们带来好处,但是也有不少问题需要解决
    发表于 10-22 11:43

    求助labview 多线程编程,应用场景,多个串口执行同一个测试程序,如何设计最合理

    求助labview 多线程编程,应用场景,多个串口执行同一个测试程序,如何设计最合理,在一个界面显示,多个串口的执行是异步执行
    发表于 09-14 11:09

    Linux多线程线程同步

    。同一进程内的线程共享进程的地址空间。通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。调度和切换:
    发表于 12-08 14:14

    LabVIEW中使用多线程运行速度是否更快

    LabVIEW中使用多线程运行速度是否更快问题: 如果使用了多线程,应用程序是不是跑的更快些?解答:这个取决于应用程序。如果应用程序中的任务顺序执行,不会看到任何改善。比方说,程序
    发表于 02-01 13:14

    QNX环境下多线程编程

    介绍了QNX 实时操作系统和多线程编程技术,包括线程同步的方法、多线程程序的分析步骤、线程基本程序结构以及实用编译方法。QNX 是由加拿大
    发表于 08-12 17:37 30次下载

    Linux多线程同步方法

    线程对共享相同内存操作时,就会出现多个线程对同一资源的使用,为此,需要对这些线程进行同步,以确保它们在访问共享内存的时候不会访问到无效的数值。
    发表于 08-08 14:17 1958次阅读

    多线程与聊天室程序的创建

    多线程程序的编写,多线程应用中容易出现的问题。互斥对象的讲解,如何采用互斥对象来实现多线程同步。如何利用命名互斥对象保证应用程序只有一个实例运行。应用
    发表于 05-16 15:22 0次下载

    java多线程同步方法

    操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程
    发表于 09-27 13:19 0次下载

    Linux多线程同步

    激活状态,从而让多个函数的操作同时运行。即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果。如下图所示,就是一个多线程的流程:main()到f
    发表于 04-02 14:47 328次阅读

    多线程两种同步方式的操作方法分析

    线程对共享相同内存操作时,就会出现多个线程对同一资源的使用,为此,需要对这些线程进行同步,以确保它们在访问共享内存的时候不会访问到无效的数值。
    的头像 发表于 06-26 14:57 1553次阅读
    <b class='flag-5'>多线程</b>两种<b class='flag-5'>同步</b>方式的操作方法分析

    多线程的情况下如何对一个值进行 a++ 操作

    多线程的情况下,对一个值进行 a++ 操作,会出现什么问题? a++ 的问题 先写个 demo 的例子。把 a++ 放入多线程中运行一下。定义 10 个
    的头像 发表于 10-13 11:17 297次阅读
    在<b class='flag-5'>多线程</b>的情况下如何对一个值<b class='flag-5'>进行</b> a++ 操作

    如何使用pthread_barrier_xxx系列函数来实现多线程之间的同步

    在Linux系统中提供了多种同步机制,本文主要讲讲如何使用pthread_barrier_xxx系列函数来实现多线程之间进行同步的方法。
    的头像 发表于 10-23 14:43 332次阅读
    如何使用pthread_barrier_xxx系列函数来实现<b class='flag-5'>多线程</b>之间的<b class='flag-5'>同步</b>?

    多线程同步的几种方法

    多线程同步是指在多个线程并发执行的情况下,为了保证线程执行的正确性和一致性,需要采用特定的方法来协调线程之间的执行顺序和共享资源的访问。下面
    的头像 发表于 11-17 14:16 521次阅读

    多线程如何保证数据的同步

    。本文将详细介绍多线程数据同步的概念、问题、以及常见的解决方案。 一、多线程数据同步概念 在多线程编程中,数据
    的头像 发表于 11-17 14:22 349次阅读

    mfc多线程编程实例

    (图形用户界面)应用程序的开发。在这篇文章中,我们将重点介绍MFC中的多线程编程。 多线程编程在软件开发中非常重要,它可以实现程序的并发执行,提高程序的效率和响应速度。MFC提供了丰富的多线程支持,可以轻松地实现
    的头像 发表于 12-01 14:29 504次阅读