线程阻塞问题

Gary_Cui_1st 2016-07-18 12:23:10
封装了个SocketClient和HttpAccessManager,SocketClient其主要功能是socket收发数据,HttpAccessManager其主要功能是http下载上传。

写了个测试代码,其主要测试过程为
Thread-A 创建SocketClient, HttpAccessManager,并创建Thread-B和Thread-C,Thread-B负责处理SocketClient的工作流程——主要为读写socket,Thread-C负责处理HttpAccessManager工作流程——主要为上传下载请求。

在Thread-A里大量调用SocketClient的send函数,负责往SocketClient的一个sendDataQueue里压数据,等调度到Thread-B里,开始socket的真正读写。当在Thread-B线程里收到服务端收到的特定下载http文件的时候,其负责往HttpAccessManager里压任务,当调度到Thread-C时,开始真正的Http动作,当任务完成,其会调用SocketClient的OnFinishReply函数

Thread-A里的被调用的send函数往queue里压数据有lock-unlock动作
Thread-B里读queue,然后往socket里写也有lock-unlock动作
Thread-C的http任务完成后,其会调用OnfinishReply,其也会有lock-unlock的动作


现在发现,当锁类型为默认锁时,线程会阻塞,阻塞的流程是,Thread-C调用OnFinishedReply,然后lock了,切换到Thread-A线程,调度到send函数,其内部也做lock动作,Thread-A阻塞,然后尝试调度到Thread-B,Thread-B也尝试lock,Thread-B也阻塞,然后就没有然后了,Thread-C也等不到unlock的,就都阻塞了。

但是如果锁类型修改为ERRORCHECK锁的话,就不会出现这样的阻塞。

下图为流程图


...全文
275 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
hlx_beat 2016-07-19
  • 打赏
  • 举报
回复
使用libevent 异步非阻塞 经测试服务端65534个连接 客户端3秒一个消息 无压力 服务端单线程 端口是usinged short也就是最大数65535 本身占一个端口
Gary_Cui_1st 2016-07-19
  • 打赏
  • 举报
回复
引用 6 楼 dayuanyuan1989 的回复:
[quote=引用 5 楼 xgywd 的回复:] A写数据A没写完, B读数据A有锁.等待. C读数据数据B没读完 A写完数据A B读数据A写数据B有锁等待 C调用B函数被锁 BC互锁 上面只是一个意思.因为你的描述不轻,不知道你A对什么数据操作.B对什么数据操作.C对什么数据操作.不过写出个意思.你自己可以分析. 最大可能 BC互锁 解决办法:像你这样多个数据在线程中交插读写,那必须注意一件事情,那就是不要以阻塞的形式来等待. 判断一下,能读就读,不能读就跳过.不要等,一等就死.
谢谢回复,的确,我也感觉我描述不清。 --------------------------------------------------------------------- 我封装的是一个SocketClient类,其可以异步发送数据,接收数据,封装了个send函数,其内部有锁,锁的作用是当调用send函数时,往queue里压数据,锁保护queue,保证queue能同步压数据。其还有一个方法OnFinishReply,当有httpAccessManager任务完成一个后,负责通知socketClient。 线程A为test.cpp即主线程,其实例化socketClient,并且调用send函数,发送数据。当线程回到socketClient所在的线程时,socketClient即Thread-B,其会读queue里的数据,往socket里写,这里也有一个锁,其为了保证能完整的读queue里的数据 在test.cpp里我还实例化了一个httpAccessManager,其负责http下载,所在线程为Thread-C,下载完成一个后,其会调用socketClient.OnFinishReply(),通知下载完成,在这个函数内部也有一个锁。 这三个有锁的地方都是同一把锁,即互斥锁 -----------------------------------------割----------------------------------------------- 现在的操作手顺是:我从socketClient和server连接起来后,大量的send数据,以及调用服务端给客户端发送任务让他调用httpAccessManager去进行http下载。 锁的类型是默认锁 阻塞的时机是:send数据都能正常,但是等httpAccessManager(Thread-C)完成第一个下载任务时,其会调用socketClient.OnFinishReply,其内部有互斥锁,其会加上锁,然后跑到Thread-A即test.c那个线程,去运作send函数,其内部也有锁,thread-a发现thread-C以及lock住了,线程thread-a阻塞。调度到thread-B,和线程A一样,thread-b也阻塞,然后就全部阻塞了。 阻塞的现象还算明显,就是Thread-C lock了,但是没有重新调度到Thread-C里去完成unlock的工作 锁的类型如果是CheckError 就没有这样的问题,正常调度,程序完美运行 下图为Thread-C调用的FinishReply代码,其内部加锁的位置也很容易看到,该线程实在看不出有阻塞的代码。 [/quote] ---------------------------------------------必须割一下-------------------------------------- 我犯了最低级的错误,OnFinishedReply里先lock住,然后调用sendAckReply其内部也是封装的send函数,即其也有锁,就是说在同一个线程锁住了两次。
Gary_Cui_1st 2016-07-19
  • 打赏
  • 举报
回复
引用 5 楼 xgywd 的回复:
A写数据A没写完, B读数据A有锁.等待. C读数据数据B没读完
A写完数据A B读数据A写数据B有锁等待 C调用B函数被锁

BC互锁


上面只是一个意思.因为你的描述不轻,不知道你A对什么数据操作.B对什么数据操作.C对什么数据操作.不过写出个意思.你自己可以分析.
最大可能 BC互锁

解决办法:像你这样多个数据在线程中交插读写,那必须注意一件事情,那就是不要以阻塞的形式来等待. 判断一下,能读就读,不能读就跳过.不要等,一等就死.


谢谢回复,的确,我也感觉我描述不清。
---------------------------------------------------------------------
我封装的是一个SocketClient类,其可以异步发送数据,接收数据,封装了个send函数,其内部有锁,锁的作用是当调用send函数时,往queue里压数据,锁保护queue,保证queue能同步压数据。其还有一个方法OnFinishReply,当有httpAccessManager任务完成一个后,负责通知socketClient。

线程A为test.cpp即主线程,其实例化socketClient,并且调用send函数,发送数据。当线程回到socketClient所在的线程时,socketClient即Thread-B,其会读queue里的数据,往socket里写,这里也有一个锁,其为了保证能完整的读queue里的数据

在test.cpp里我还实例化了一个httpAccessManager,其负责http下载,所在线程为Thread-C,下载完成一个后,其会调用socketClient.OnFinishReply(),通知下载完成,在这个函数内部也有一个锁。

这三个有锁的地方都是同一把锁,即互斥锁
-----------------------------------------割-----------------------------------------------
现在的操作手顺是:我从socketClient和server连接起来后,大量的send数据,以及调用服务端给客户端发送任务让他调用httpAccessManager去进行http下载。

锁的类型是默认锁
阻塞的时机是:send数据都能正常,但是等httpAccessManager(Thread-C)完成第一个下载任务时,其会调用socketClient.OnFinishReply,其内部有互斥锁,其会加上锁,然后跑到Thread-A即test.c那个线程,去运作send函数,其内部也有锁,thread-a发现thread-C以及lock住了,线程thread-a阻塞。调度到thread-B,和线程A一样,thread-b也阻塞,然后就全部阻塞了。

阻塞的现象还算明显,就是Thread-C lock了,但是没有重新调度到Thread-C里去完成unlock的工作

锁的类型如果是CheckError
就没有这样的问题,正常调度,程序完美运行

下图为Thread-C调用的FinishReply代码,其内部加锁的位置也很容易看到,该线程实在看不出有阻塞的代码。

五号智能 2016-07-19
  • 打赏
  • 举报
回复
A写数据A没写完, B读数据A有锁.等待. C读数据数据B没读完 A写完数据A B读数据A写数据B有锁等待 C调用B函数被锁 BC互锁 上面只是一个意思.因为你的描述不轻,不知道你A对什么数据操作.B对什么数据操作.C对什么数据操作.不过写出个意思.你自己可以分析. 最大可能 BC互锁 解决办法:像你这样多个数据在线程中交插读写,那必须注意一件事情,那就是不要以阻塞的形式来等待. 判断一下,能读就读,不能读就跳过.不要等,一等就死.
ID870177103 2016-07-18
  • 打赏
  • 举报
回复
引用 3 楼 dayuanyuan1989 的回复:
[quote=引用 1 楼 ID870177103 的回复:] 不是很明白你说的流程,你的lock对象只是queue吗,还是还有socket的 按你的设计说,A给B发任务,B对A负责,B给C发任务,C对B负责,那为什么C和A扯上关系了呢,而且用A来启动C,是怎么保证B在运行中C没有结束呢 如果只是想让ABC的操作并行运行,它们维护同一个queue怎么会死锁
线程A就是测试代码所在的主线程,线程B就是Thread thread(SocketClient)所在的线程,负责socket处理,线程C就是Thread downloadThread(NetworkAccessManager)线程,负责http处理,当线程C的http的一个任务处理完了,其会调用socketClient.onFinishReply,告诉socketClient,http的任务完成了[/quote] 你给的代码没有看到重点,锁这种东西针对的是共享对象的读写,重点是写 socketClient.send socketClient.onFinishReply 和线程B的读写操作是怎么实现的 是否因为线程需要两个临界对象才造成死锁或者其他原因 而且你的Thread是自己封装的?
Gary_Cui_1st 2016-07-18
  • 打赏
  • 举报
回复
引用 1 楼 ID870177103 的回复:
不是很明白你说的流程,你的lock对象只是queue吗,还是还有socket的 按你的设计说,A给B发任务,B对A负责,B给C发任务,C对B负责,那为什么C和A扯上关系了呢,而且用A来启动C,是怎么保证B在运行中C没有结束呢 如果只是想让ABC的操作并行运行,它们维护同一个queue怎么会死锁
线程A就是测试代码所在的主线程,线程B就是Thread thread(SocketClient)所在的线程,负责socket处理,线程C就是Thread downloadThread(NetworkAccessManager)线程,负责http处理,当线程C的http的一个任务处理完了,其会调用socketClient.onFinishReply,告诉socketClient,http的任务完成了
Gary_Cui_1st 2016-07-18
  • 打赏
  • 举报
回复
引用 1 楼 ID870177103 的回复:
不是很明白你说的流程,你的lock对象只是queue吗,还是还有socket的 按你的设计说,A给B发任务,B对A负责,B给C发任务,C对B负责,那为什么C和A扯上关系了呢,而且用A来启动C,是怎么保证B在运行中C没有结束呢 如果只是想让ABC的操作并行运行,它们维护同一个queue怎么会死锁
我说的比较累赘,我给你看一下测试代码,你就能明白了。

#ifndef NO_TSET_SSL_SOCKET_CLIENT
	SslSocketClient socketClient(ip, port, true, true);
#else
    SocketClient socketClient(ip, port, true, true);
#endif    
    Thread thread(socketClient);
    socketClient.init();
    thread.start();
    
    int counter = 0;
    TransferFileInfo transferFileInfo;
    TranserFileHttpsInfo transferFileHttpInfo;

#ifndef NO_TEST_HTTP_DOWNLOAD
    NetworkAccessManager downloadManager(true, true);
    Thread downloadThread(downloadManager);
    downloadManager.init();
    socketClient.setDownloadManager(&downloadManager);
    downloadThread.start();
#endif

#ifndef NO_TEST_SEND
    counter = 1000;
    while(counter--) {
        std::string text = "wo de ming zi jiao cui-yuanyuan, wo lai zi China, fei chang gao xin ren shi ni men! ";
        std::string lap(counter, 'x');
        text += lap;
        socketClient.send((unsigned char*)text.c_str(), text.length(), MSGFLAGID_COMMAND);
    }
#endif
ID870177103 2016-07-18
  • 打赏
  • 举报
回复
不是很明白你说的流程,你的lock对象只是queue吗,还是还有socket的 按你的设计说,A给B发任务,B对A负责,B给C发任务,C对B负责,那为什么C和A扯上关系了呢,而且用A来启动C,是怎么保证B在运行中C没有结束呢 如果只是想让ABC的操作并行运行,它们维护同一个queue怎么会死锁

64,282

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧