免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: alwaysR9
打印 上一主题 下一主题

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

论坛徽章:
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
21 [报告]
发表于 2015-09-15 14:47 |只看该作者
本帖最后由 cokeboL 于 2015-09-15 14:47 编辑

回复 20# folklore


    tks,那我循环write就不算画蛇添足了

论坛徽章:
0
22 [报告]
发表于 2015-09-15 23:24 |只看该作者
回复 6# alwaysR9
我创建了26个线程,每个线程写了4095个A(或B或C)到socket,发现有一行只有4032个'L'
服务端代码如下:
  1. #include<stdio.h>
  2. #include<sys/socket.h>
  3. #include<netinet/in.h>
  4. #include<arpa/inet.h>
  5. #include<pthread.h>
  6. #include<string.h>
  7. #define PORT 9998
  8. #define MAX_PTHREAD 26
  9. #define NUMCHAR 4096
  10. #define LOOP 1000
  11. static int workfd;
  12. void *pthreadDo(void *arg);
  13. int main() {
  14. printf("begin socket\n");
  15.         int sockfd=socket(AF_INET, SOCK_STREAM, 0);

  16.         struct sockaddr_in sockaddr={0};
  17.         sockaddr.sin_family=AF_INET;
  18.         sockaddr.sin_port=htons(PORT);
  19.         inet_pton(AF_INET, "127.0.0.1", &(sockaddr.sin_addr));

  20. printf("begin bind\n");
  21.         bind(sockfd, (const struct sockaddr*)&sockaddr, sizeof(struct sockaddr_in));

  22. printf("begin listen\n");
  23.         listen(sockfd, SOMAXCONN);

  24. printf("begin accept\n");
  25.         workfd=accept(sockfd, NULL, NULL);
  26. printf("get workfd %d\n", workfd);

  27.         int i=0;
  28.         pthread_t ntid;
  29.         for (i=0; i<MAX_PTHREAD; i++) {
  30.                 pthread_create(&ntid, NULL, pthreadDo,&i);
  31.         }
  32.         while (1) {}

  33.         return 0;
  34. }

  35. void *pthreadDo(void *arg) {
  36.         char c=*((int*)arg) + 'A';
  37.         char str[NUMCHAR]={0};
  38.         memset(str, c, NUMCHAR-1);
  39.         str[NUMCHAR-1]='\n';
  40.         int i=0;
  41.         for (i=0; i<LOOP; i++) {
  42.                 write(workfd, str, NUMCHAR);
  43.         }
  44.         return NULL;
  45. }
复制代码
客户端直接使用telnet 127.0.0.1 9998>a.txt
然后使用grep找出这一行(只有4032个'L'的这一行)grep -n "[L]\{4032\}" a.txt|grep -v "[L]\{4033\}"


   

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

本楼作废, 这个函数不是write函数的源码, 实际上write函数通过调用sys_write系统调用来完成的.


我找到的write函数源码:
  1. int write(struct file *filp, void *data, size_t size) {
  2.     int rc;

  3.     if (!filp) return -EINVAL;
  4.     if (!data && size > 0) return -EINVAL;
  5.     if (filp->flags == O_RDONLY) return -EACCES;

  6.     if (!filp->fs->ops->write) return -ENOSYS;
  7.     if (lock_fs(filp->fs, FSOP_WRITE) < 0) return -ETIMEOUT;
  8.     if (filp->flags & O_TEXT) {
  9.         rc = write_translated(filp, data, size);
  10.     } else {
  11.         rc = filp->fs->ops->write(filp, data, size, filp->pos);
  12.         if (rc > 0) filp->pos += rc;
  13.     }
  14.     unlock_fs(filp->fs, FSOP_WRITE);
  15.     return rc;
  16. }
复制代码
1. write函数加锁了 lock_fs() , 但是我不清楚这个是不是和pthread_mutex一样的功能.
2. 根据filp->flag的不同, write分成两种情况, 我也不太清楚 O_TEXT表示什么文件属性.
3. filp->fs->ops->write(filp, data, size, filp->pos) 这个函数的定义没有找到, 我明天再找找.



write_translated()函数的定义:
  1. static int write_translated(struct file *filp, void *data, size_t size) {
  2.   char *buf;
  3.   char *p, *q;
  4.   int rc;
  5.   int lfcnt;
  6.   int bytes;
  7.   char lfbuf[LFBUFSIZ];

  8.   // Translate LF to CR/LF on output
  9.   buf = (char *) data;
  10.   p = buf;
  11.   bytes = lfcnt = 0;

  12.   while ((unsigned) (p - buf) < size) {
  13.     // Fill the buffer, except maybe last char
  14.     q = lfbuf;
  15.     while ((unsigned) (q - lfbuf) < LFBUFSIZ - 1 && (unsigned) (p - buf) < size) {
  16.       char ch = *p++;
  17.       if (ch == LF) {
  18.         lfcnt++;
  19.         *q++ = CR;
  20.       }
  21.       *q++ = ch;
  22.     }

  23.     // Write the buffer and update total
  24.     rc = filp->fs->ops->write(filp, lfbuf, q - lfbuf, filp->pos);
  25.     if (rc > 0) filp->pos += rc;
  26.     if (rc < 0) return rc;
  27.     bytes += rc;
  28.     if (rc < q - lfbuf) break;
  29.   }

  30.   return bytes - lfcnt;
  31. }
复制代码

论坛徽章:
0
24 [报告]
发表于 2015-09-16 01:32 |只看该作者
回复 23# alwaysR9


    POSIX 定义的线程安全(原子性)和你理解的线程安全(原子性)可能不是一回事,特别在发生 short write 的时候。

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

回复 22# sculida


    谢谢你的程序,我用你的程序发现问题了, 看来多线程write socket原来也是不安全的

   

    第2486行的字母G只有52个, 而正常每行应该包含4095个可见字符.
    26000行输出中, 一共有115行出现问题, 或者超出4095个字符, 或者少于4095个字符.
    ps. 你的程序有一处bug : pthread_create(&tid,  NULL,  proc,  &i); 最后一个参数不应该传地址,  传地址使主线程和子线程共享变量 i , 而主线程里 i 在不断变化, 没有加同步保护.

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
26 [报告]
发表于 2015-09-16 16:51 |只看该作者
write是线程安全的,但write的目的地不一定。

论坛徽章:
1
2015亚冠之阿尔艾因
日期:2015-08-24 15:46:57
27 [报告]
发表于 2015-09-18 17:35 |只看该作者
我这边试了一下实验1似乎没有覆盖

论坛徽章:
1
程序设计版块每日发帖之星
日期:2015-09-22 06:20:00
28 [报告]
发表于 2015-09-19 01:12 |只看该作者
write执行后,返回前,就会把fd的文件位移给进行向后移,写和移位不是原子操作,非线程安全是因为这里。你可以用pwrite,这个就是位移加写是线程安全的。至于说socket,那是另外的设计了。

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-12-23 06:20:00每日论坛发贴之星
日期:2015-12-23 06:20:00
29 [报告]
发表于 2015-09-19 11:24 |只看该作者
Museless 发表于 2015-09-19 01:12
write执行后,返回前,就会把fd的文件位移给进行向后移,写和移位不是原子操作,非线程安全是因为这里。你可 ...


这是sys_write源码, 内核版本4.2:
  1. SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
  2. 579 {
  3. 580         struct fd f = fdget_pos(fd);
  4. 581         ssize_t ret = -EBADF;
  5. 582
  6. 583         if (f.file) {
  7. 584                 loff_t pos = file_pos_read(f.file); // 获得文件指针的位置
  8. 585                 ret = vfs_write(f.file, buf, count, &pos); // 从文件指针处开始写文件
  9. 586                 if (ret >= 0)                                   // 接下来3行,用来更新文件指针
  10. 587                         file_pos_write(f.file, pos);
  11. 588                 fdput_pos(f);
  12. 589         }
  13. 590
  14. 591         return ret;
  15. 592 }
复制代码
从源码可以看到sys_write函数中,   获得文件指针,写文件,更新文件指针  3个操作是非原子的.

下面是vfs_write函数的源码, 这两个函数值得注意: file_start_write(), file_end_write()
  1. ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
  2. 524 {
  3. 525         ssize_t ret;
  4. 526
  5. 527         if (!(file->f_mode & FMODE_WRITE))
  6. 528                 return -EBADF;
  7. 529         if (!(file->f_mode & FMODE_CAN_WRITE))
  8. 530                 return -EINVAL;
  9. 531         if (unlikely(!access_ok(VERIFY_READ, buf, count)))
  10. 532                 return -EFAULT;
  11. 533
  12. 534         ret = rw_verify_area(WRITE, file, pos, count);
  13. 535         if (ret >= 0) {
  14. 536                 count = ret;
  15. 537                 file_start_write(file);     // 我推测该函数的功能是上锁
  16. 538                 ret = __vfs_write(file, buf, count, pos);  // 从文件指针处开始写文件
  17. 539                 if (ret > 0) {
  18. 540                         fsnotify_modify(file);
  19. 541                         add_wchar(current, ret);
  20. 542                 }
  21. 543                 inc_syscw(current);
  22. 544                 file_end_write(file);       // 我推测该函数的功能是释放锁
  23. 545         }
  24. 546
  25. 547         return ret;
  26. 548 }
复制代码
最后是file_end_write函数的源码:
  1. static inline void file_end_write(struct file *file)
  2. 2476 {
  3. 2477         if (!S_ISREG(file_inode(file)->i_mode))   // 判断是否是常规文件, 若不是常规文件, 直接返回
  4. 2478                 return;
  5. 2479         __sb_end_write(file_inode(file)->i_sb, SB_FREEZE_WRITE);  // 该函数的实现在下一个函数中
  6. 2480 }
复制代码
  1. void __sb_end_write(struct super_block *sb, int level)
  2. 1163 {
  3. 1164         percpu_up_read(sb->s_writers.rw_sem + level-1); // 该函数实现在下一个函数中
  4. 1165 }
复制代码
  1. void percpu_up_read(struct percpu_rw_semaphore *brw)
  2. 105 {
  3. 106         rwsem_release(&brw->rw_sem.dep_map, 1, _RET_IP_); // 函数名中的 rwsem 表示 "读写信号量", 从函数名可以推测该函数功能是释放锁
  4. 107
  5. 108         if (likely(update_fast_ctr(brw, -1)))
  6. 109                 return;
  7. 110
  8. 111         /* false-positive is possible but harmless */
  9. 112         if (atomic_dec_and_test(&brw->slow_read_ctr))
  10. 113                 wake_up_all(&brw->write_waitq);                             // **等待队列
  11. 114 }
复制代码
我对源码的理解可以用一个流程来说明:
  1. sys_write (file):
  2.     1. 获得文件指针的位置
  3.     2. 从文件指针处开始写入
  4.         2.1 对常规文件加锁, 对非常规文件不加锁
  5.         2.2 写文件        
  6.         2.3 对常规文件解锁, 对非常规文件不解锁
  7.     3. 更新文件指针的位置
复制代码
不知道我对源码的解读是否正确, 欢迎大家来拍砖

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-12-23 06:20:00每日论坛发贴之星
日期:2015-12-23 06:20:00
30 [报告]
发表于 2015-09-19 11:29 |只看该作者
何必抱怨 发表于 2015-09-18 17:35
我这边试了一下实验1似乎没有覆盖


在我笔记本上每次运行第一个实验都会出现覆盖问题, 可能我的机器运行比较慢导致的, cpu只有2.4GHZ
你的机器什么配置?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP