免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12345下一页
最近访问板块 发新帖
查看: 14783 | 回复: 40
打印 上一主题 下一主题

[C] linux write 函数 是否是 线程安全的? [复制链接]

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-12-23 06:20:00每日论坛发贴之星
日期:2015-12-23 06:20:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2015-09-10 17:45 |只看该作者 |倒序浏览
我做了两个实验:

第一个实验,创建一个本地文件,然后用5个线程对这个文件进行写入,结果前面的写入内容被后面的写入内容覆盖;对write函数加锁之后结果就正常了,就似乎验证了write函数是非线程安全的。

第二个实验,创建一个客户端的TCP socket,然后用5个线程对这个socket进行写入;服务器端把内容读取出来并打印,发现打印结果与客户端发送内容一致,没有出现异常,似乎说明write TCP socket是线程安全的。

我的问题是:
如果write不是线程安全的,为什么写TCP socket却正常,是否因为系统为socket操作加锁了?

实验代码如下
  1. #include        <unistd.h>
  2. #include        <errno.h>
  3. #include        <pthread.h>

  4. #include        <sys/socket.h>
  5. #include        <netinet/in.h>
  6. #include        <sys/types.h>
  7. #include        <sys/select.h>
  8. #include        <sys/stat.h>
  9. #include        <arpa/inet.h>
  10. #include        <fcntl.h>

  11. #include        <stdio.h>
  12. #include        <stdlib.h>
  13. #include        <string.h>

  14. #define BUFF_SIZE 1024

  15. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  16. struct ThreadArg
  17. {
  18.         int id;
  19.         int fd;
  20. };

  21. void*
  22. proc(void* arg)
  23. {
  24.         struct ThreadArg* p_arg = (struct ThreadArg*) arg;

  25.         char msg[BUFF_SIZE];
  26.         int  n_msg;
  27.         n_msg = snprintf(msg, BUFF_SIZE, "thread_%d\n", p_arg->id);

  28.         int i;
  29.         for (i = 0; i < 5; ++ i)
  30.         {
  31.                 //pthread_mutex_lock(& mutex);
  32.                 if (write(p_arg->fd, msg, n_msg) < 0)
  33.                         perror("thread %d write fail");
  34.                 //pthread_mutex_unlock(& mutex);
  35.         }
  36. }

  37. int
  38. open_socket(char* ip)
  39. {
  40.         int                                 connfd;
  41.         struct sockaddr_in         serv_addr;

  42.         if ( (connfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  43.                 return -1;

  44.         memset(&serv_addr, 0, sizeof(serv_addr));
  45.         serv_addr.sin_family = AF_INET;
  46.         serv_addr.sin_port         = htons(9999);
  47.         inet_pton(AF_INET, ip, &serv_addr.sin_addr);

  48.         if ( connect(connfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
  49.                 return -1;

  50.         return connfd;
  51. }

  52. int
  53. open_file(char* file_name)
  54. {
  55.         return open(file_name, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
  56. }

  57. int
  58. main(int argc, char** argv)
  59. {
  60.         int  fd;

  61.         pthread_t tids[5];
  62.         struct ThreadArg targ[5];

  63.         //if ( (fd = open_socket("127.0.0.1")) < 0) // 实验二
  64.         //        exit(1);

  65.         if ( (fd = open_file("data")) < 0) // 实验一
  66.                 exit(1);

  67.         /* start child threads */
  68.         int i;
  69.         for (i = 0; i < 5; ++ i)
  70.         {
  71.                 targ[i].id = i;
  72.                 targ[i].fd = fd;
  73.                 pthread_create(tids+i, NULL, proc, targ+i);
  74.         }

  75.         for (i = 0; i < 5; ++ i)
  76.                 pthread_join(tids[i], NULL);

  77.         close(fd);
  78.         exit(0);
  79. }
复制代码
实验二 需要的服务器程序代码如下:
  1. #include        <stdio.h>
  2. #include        <stdlib.h>
  3. #include        <string.h>

  4. #include        <sys/types.h>
  5. #include        <sys/socket.h>
  6. #include        <netinet/in.h>
  7. #include        <sys/epoll.h>
  8. #include        <fcntl.h>

  9. #include        <errno.h>

  10. const int MAX_EVENTS = 1024;
  11. const int BUFF_SIZE = 1024;

  12. void err_quit(const char* msg) {
  13.         printf("%s, error code = %d\n", msg, errno);
  14.         exit(1);
  15. }

  16. void err_sys(const char* msg) {
  17.         printf("%s, error code = %d\n", msg, errno);
  18. }

  19. int create_and_bind(int port_no) {
  20.         int listen_fd;
  21.         struct sockaddr_in serv_addr;

  22.         if ( (listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  23.                 return -1;

  24.         memset(&serv_addr, 0, sizeof(serv_addr));
  25.         serv_addr.sin_family      = AF_INET;
  26.         serv_addr.sin_addr.s_addr = INADDR_ANY;
  27.         serv_addr.sin_port        = htons(port_no);

  28.         if ( bind(listen_fd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0)
  29.                 return -1;

  30.         return listen_fd;
  31. }

  32. int communicate(const int fd)
  33. {
  34.         int  n_msg = 9;
  35.         char msg[BUFF_SIZE];

  36.         int count = 0;
  37.         int n_read;
  38.         while ( (n_read = read(fd, msg, n_msg)) > 0)
  39.         {
  40.                 msg[n_msg] = 0;
  41.                 printf("%s", msg);
  42.                 ++ count;
  43.         }

  44.         printf("msg number = %d\n", count);

  45.         if (n_read < 0)
  46.                 return -1;
  47.         return 0;
  48. }

  49. int main(int argc, char** argv) {

  50.         int listen_fd;
  51.         int conn_fd;

  52.         /* create and bind listening socket */
  53.         listen_fd = create_and_bind(9999);
  54.         if (listen_fd < 0)
  55.                 err_quit("create and bind listening socket failed!");

  56.         /* listening */
  57.         listen(listen_fd, 100);

  58.         while (1) {
  59.                 if ( (conn_fd = accept(listen_fd, NULL, NULL)) < 0)
  60.                         err_sys("accept connection socket failed!");
  61.                 else
  62.                         if (communicate(conn_fd) < 0)
  63.                                 perror("read socket fail");
  64.                         else
  65.                                 close(conn_fd);
  66.         }

  67.         close(listen_fd);
  68.         exit(0);
  69. }
复制代码

论坛徽章:
59
2015年亚洲杯之约旦
日期:2015-01-27 21:27:392015年亚洲杯之日本
日期:2015-02-06 22:09:41拜羊年徽章
日期:2015-03-03 16:15:432015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015元宵节徽章
日期:2015-03-06 15:50:392015年亚洲杯之阿联酋
日期:2015-03-19 17:39:302015年亚洲杯之中国
日期:2015-03-23 18:52:23巳蛇
日期:2014-12-14 22:44:03双子座
日期:2014-12-10 21:39:16处女座
日期:2014-12-02 08:03:17天蝎座
日期:2014-07-21 19:08:47
2 [报告]
发表于 2015-09-10 18:04 |只看该作者
系统调用都是线程安全的。

论坛徽章:
11
2015年迎新春徽章
日期:2015-03-04 09:55:282017金鸡报晓
日期:2017-02-08 10:39:4215-16赛季CBA联赛之辽宁
日期:2016-12-15 10:24:1715-16赛季CBA联赛之佛山
日期:2016-11-30 09:04:2015-16赛季CBA联赛之江苏
日期:2016-04-29 15:56:1215-16赛季CBA联赛之同曦
日期:2016-04-12 13:21:182016猴年福章徽章
日期:2016-02-18 15:30:3415-16赛季CBA联赛之山东
日期:2016-02-16 11:37:52每日论坛发贴之星
日期:2016-02-07 06:20:00程序设计版块每日发帖之星
日期:2016-02-07 06:20:0015-16赛季CBA联赛之新疆
日期:2018-01-09 16:25:37
3 [报告]
发表于 2015-09-10 18:19 |只看该作者
貌似方式错了,多线程应该不是用来做IO的, 而是用于处理器消耗型的任务并发

论坛徽章:
324
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
4 [报告]
发表于 2015-09-10 22:30 |只看该作者
应该说write是线程安全的,但最终的结果如何取决于“文件”的实现。

write写普通文件,指定偏移量不重叠写,最终结果是肯定的
write写TCP socket,你每次写个几十K,结果很可能就会不一样了

论坛徽章:
0
5 [报告]
发表于 2015-09-11 00:28 |只看该作者
本帖最后由 sculida 于 2015-09-15 23:29 编辑

其实往socket_fd里write也不安全,比如我把你的msg改成thread_%d0123456789001234567890012345678900123456789001234567890
然后每个线程写100遍。你自己执行看看,是不是文字也错乱了。我就得到下面这一段输出
---------------------------------------
thread_1 123456789012345678901234567890123456890
thread_1 123456789012345678901234567890123456890
thread_1 123456789012345678901234567890123456890
thread_1 123456789012345678901234567890123456890
23thread_1 123456789012345678901234567890123456890
thread_1 123456789012345678901234567890123456890
---------------------------------
关于线程与io,可以使用pwrite和pread,参见《unix高级环境编程》的章节:12.10 线程与IO

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-12-23 06:20:00每日论坛发贴之星
日期:2015-12-23 06:20:00
6 [报告]
发表于 2015-09-11 15:40 |只看该作者
本帖最后由 alwaysR9 于 2015-09-11 15:45 编辑

回复 5# sculida


    我重新测试了write socket, 5个线程,每个线程每次写2KB数据到socket,写100次。
   服务器收到的数据依然是正确的顺序
   你再看看你改的程序,或者仔细测试一下,会不会你在测试输出时出错了。服务器端n_msg变量的值一定要设对,否则read出客户端发送的消息长度不对,就会造成输出看起来像乱序了一样

论坛徽章:
36
子鼠
日期:2013-08-28 22:23:29黄金圣斗士
日期:2015-12-01 11:37:51程序设计版块每日发帖之星
日期:2015-12-14 06:20:00CU十四周年纪念徽章
日期:2015-12-22 16:50:40IT运维版块每日发帖之星
日期:2016-01-25 06:20:0015-16赛季CBA联赛之深圳
日期:2016-01-27 10:31:172016猴年福章徽章
日期:2016-02-18 15:30:3415-16赛季CBA联赛之福建
日期:2016-04-07 11:25:2215-16赛季CBA联赛之青岛
日期:2016-04-29 18:02:5915-16赛季CBA联赛之北控
日期:2016-06-20 17:38:50技术图书徽章
日期:2016-07-19 13:54:03程序设计版块每日发帖之星
日期:2016-08-21 06:20:00
7 [报告]
发表于 2015-09-11 16:46 |只看该作者
回复 6# alwaysR9


    那么点数据和次数,能测出的概率很小。。

论坛徽章:
36
子鼠
日期:2013-08-28 22:23:29黄金圣斗士
日期:2015-12-01 11:37:51程序设计版块每日发帖之星
日期:2015-12-14 06:20:00CU十四周年纪念徽章
日期:2015-12-22 16:50:40IT运维版块每日发帖之星
日期:2016-01-25 06:20:0015-16赛季CBA联赛之深圳
日期:2016-01-27 10:31:172016猴年福章徽章
日期:2016-02-18 15:30:3415-16赛季CBA联赛之福建
日期:2016-04-07 11:25:2215-16赛季CBA联赛之青岛
日期:2016-04-29 18:02:5915-16赛季CBA联赛之北控
日期:2016-06-20 17:38:50技术图书徽章
日期:2016-07-19 13:54:03程序设计版块每日发帖之星
日期:2016-08-21 06:20:00
8 [报告]
发表于 2015-09-11 16:52 |只看该作者
只考虑原理,阻塞IO,一次写入成功的数量可能比要写入的数量小,系统调用时陷入内核,多个线程竞争一个fd,A这次写了10个字节系统调用结束后,当前调度到哪个线程的write,如果不是刚才没写完的那个,就可能出错咯

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-12-23 06:20:00每日论坛发贴之星
日期:2015-12-23 06:20:00
9 [报告]
发表于 2015-09-11 19:33 |只看该作者
回复 8# cokeboL


    我去找源码看看,只有源码能说明write有没有加锁

论坛徽章:
59
2015年亚洲杯之约旦
日期:2015-01-27 21:27:392015年亚洲杯之日本
日期:2015-02-06 22:09:41拜羊年徽章
日期:2015-03-03 16:15:432015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015元宵节徽章
日期:2015-03-06 15:50:392015年亚洲杯之阿联酋
日期:2015-03-19 17:39:302015年亚洲杯之中国
日期:2015-03-23 18:52:23巳蛇
日期:2014-12-14 22:44:03双子座
日期:2014-12-10 21:39:16处女座
日期:2014-12-02 08:03:17天蝎座
日期:2014-07-21 19:08:47
10 [报告]
发表于 2015-09-11 20:54 |只看该作者
回复 8# cokeboL


    write是原子的, 系统一定要保证这个语义, 不然这系统没法用了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP