1.Linux“线程”

进程与线程之间是有区别的,不过linux内核只提供了轻量进程的支持,未实现线程模型。Linux是一种“多进程单线程”的操作系统。Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。

大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。Linux中所谓的“线程”只是在被创建时clone了父进程的资源,因此clone出来的进程表现为“线程”,这一点一定要弄清楚。因此,Linux“线程”这个概念只有在打冒号的情况下才是最准确的。

目前Linux中最流行的线程机制为LinuxThreads,所采用的就是线程-进程“一对一”模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。LinuxThreads由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行,它实现了一种BiCapitalized面向Linux的Posix 1003.1c “pthread”标准接口。Linuxthread可以支持Intel、Alpha、MIPS等平台上的多处理器系统。

按照POSIX 1003.1c 标准编写的程序与Linuxthread 库相链接即可支持Linux平台上的多线程,在程序中需包含头文件pthread. h,在编译链接时使用命令:

gcc -D -REENTRANT -lpthread xxx. c

其中-REENTRANT宏使得相关库函数(如stdio.h、errno.h中函数) 是可重入的、线程安全的(thread-safe),-lpthread则意味着链接库目录下的libpthread.a或libpthread.so文件。使用Linuxthread库需要2.0以上版本的Linux内核及相应版本的C库(libc 5.2.18、libc 5.4.12、libc 6)。

2.“线程”控制

线程创建

进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create:

pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * 
(start_routine)(void*), void *arg);

start_routine为新线程的入口函数,arg为传递给start_routine的参数。

每个线程都有自己的线程ID,以便在进程内区分。线程ID在pthread_create调用时回返给创建线程的调用者;一个线程也可以在创建后使用pthread_self()调用获取自己的线程ID:

pthread_self (void) ;

线程退出

线程的退出方式有三:

(1)执行完成后隐式退出;

(2)由线程本身显示调用pthread_exit 函数退出;

pthread_exit (void * retval) ;

(3)被其他线程用pthread_cance函数终止:

pthread_cance (pthread_t thread) ;

在某线程中调用此函数,可以终止由参数thread 指定的线程。

如果一个线程要等待另一个线程的终止,可以使用pthread_join函数,该函数的作用是调用pthread_join的线程将被挂起直到线程ID为参数thread的线程终止:

pthread_join (pthread_t thread, void** threadreturn);

3.线程通信

线程互斥

互斥意味着“排它”,即两个线程不能同时进入被互斥保护的代码。Linux下可以通过pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。

下面的代码实现了对共享全局变量x 用互斥体mutex 进行保护的目的:

int x; // 进程中的全局变量 
pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex 
pthread_mutex_lock(&mutex); // 给互斥体变量加锁 
… //对变量x 的操作 
phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁

线程同步

同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。当多个线程协作时,相互作用的任务必须在一定的条件下同步。

Linux下的C语言编程有多种线程同步机制,最典型的是条件变量(condition variable)。pthread_cond_init用来创建一个条件变量,其函数原型为:

pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

pthread_cond_wait和pthread_cond_timedwait用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体mutex,这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。pthread_cond_wait的函数原型为:

pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_broadcast用于设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞:

pthread_cond_broadcast (pthread_cond_t *cond) ;

pthread_cond_signal则用于解除某一个等待线程的阻塞状态:

pthread_cond_signal (pthread_cond_t *cond) ;

pthread_cond_destroy 则用于释放一个条件变量的资源。

在头文件semaphore.h 中定义的信号量则完成了互斥体和条件变量的封装,按照多线程程序设计中访问控制机制,控制对资源的同步访问,提供程序设计人员更方便的调用接口。

sem_init(sem_t *sem, int pshared, unsigned int val);

这个函数初始化一个信号量sem 的值为val,参数pshared 是共享属性控制,表明是否在进程间共享。

sem_wait(sem_t *sem);

调用该函数时,若sem为无状态,调用线程阻塞,等待信号量sem值增加(post )成为有信号状态;若sem为有状态,调用线程顺序执行,但信号量的值减一。

sem_post(sem_t *sem);

调用该函数,信号量sem的值增加,可以从无信号状态变为有信号状态。

4.实例

下面我们还是以名的生产者/消费者问题为例来阐述Linux线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。生产者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有N 个,是一个环形的缓冲池。 

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #define BUFFER_SIZE 16 // 缓冲区数量
  4. struct prodcons
  5. {
  6. // 缓冲区相关数据结构
  7. int buffer[BUFFER_SIZE]; /* 实际数据存放的数组*/
  8. pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
  9. int readpos, writepos; /* 读写指针*/
  10. pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
  11. pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
  12. };
  13. /* 初始化缓冲区结构 */
  14. void init(struct prodcons *b)
  15. {
  16. pthread_mutex_init(&b->lock, NULL);
  17. pthread_cond_init(&b->notempty, NULL);
  18. pthread_cond_init(&b->notfull, NULL);
  19. b->readpos = 0;
  20. b->writepos = 0;
  21. }
  22. /* 将产品放入缓冲区,这里是存入一个整数*/
  23. void put(struct prodcons *b, int data)
  24. {
  25. pthread_mutex_lock(&b->lock);
  26. /* 等待缓冲区未满*/
  27. if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
  28. {
  29. pthread_cond_wait(&b->notfull, &b->lock);
  30. }
  31. /* 写数据,并移动指针 */
  32. b->buffer[b->writepos] = data;
  33. b->writepos++;
  34. if (b->writepos >= BUFFER_SIZE)
  35. b->writepos = 0;
  36. /* 设置缓冲区非空的条件变量*/
  37. pthread_cond_signal(&b->notempty);
  38. pthread_mutex_unlock(&b->lock);
  39. }
  40. /* 从缓冲区中取出整数*/
  41. int get(struct prodcons *b)
  42. {
  43. int data;
  44. pthread_mutex_lock(&b->lock);
  45. /* 等待缓冲区非空*/
  46. if (b->writepos == b->readpos)
  47. {
  48. pthread_cond_wait(&b->notempty, &b->lock);
  49. }
  50. /* 读数据,移动读指针*/
  51. data = b->buffer[b->readpos];
  52. b->readpos++;
  53. if (b->readpos >= BUFFER_SIZE)
  54. b->readpos = 0;
  55. /* 设置缓冲区未满的条件变量*/
  56. pthread_cond_signal(&b->notfull);
  57. pthread_mutex_unlock(&b->lock);
  58. return data;
  59. }
  60. /* 测试:生产者线程将1 到10000 的整数送入缓冲区,消费者线
  61. 程从缓冲区中获取整数,两者都打印信息*/
  62. #define OVER ( - 1)
  63. struct prodcons buffer;
  64. void *producer(void *data)
  65. {
  66. int n;
  67. for (n = 0; n < 10000; n++)
  68. {
  69. printf("%d --->\n", n);
  70. put(&buffer, n);
  71. } put(&buffer, OVER);
  72. return NULL;
  73. }
  74. void *consumer(void *data)
  75. {
  76. int d;
  77. while (1)
  78. {
  79. d = get(&buffer);
  80. if (d == OVER)
  81. break;
  82. printf("--->%d \n", d);
  83. }
  84. return NULL;
  85. }
  86. int main(void)
  87. {
  88. pthread_t th_a, th_b;
  89. void *retval;
  90. init(&buffer);
  91. /* 创建生产者和消费者线程*/
  92. pthread_create(&th_a, NULL, producer, 0);
  93. pthread_create(&th_b, NULL, consumer, 0);
  94. /* 等待两个线程结束*/
  95. pthread_join(th_a, &retval);
  96. pthread_join(th_b, &retval);
  97. return 0;
  98. }
5.WIN32、VxWorks、Linux线程类比

目前为止,笔者已经创作了《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年5~12期连载)、《深入浅出Win32多线程程序设计》(天极网技术专题)系列,我们来找出这两个系列文章与本文的共通点。

看待技术问题要瞄准其本质,不管是Linux、VxWorks还是WIN32,其涉及到多线程的部分都是那些内容,无非就是线程控制和线程通信,它们的许多函数只是名称不同,其实质含义是等价的,下面我们来列个三大操作系统共同点详细表单:

事项 WIN32 VxWorks Linux
线程创建 CreateThread taskSpawn pthread_create
线程终止 执行完成后退出;线程自身调用ExitThread函数即终止自己;被其他线程调用函数TerminateThread函数 执行完成后退出;由线程本身调用exit退出;被其他线程调用函数taskDelete终止 执行完成后退出;由线程本身调用pthread_exit 退出;被其他线程调用函数pthread_cance终止
获取线程ID GetCurrentThreadId taskIdSelf pthread_self
创建互斥 CreateMutex semMCreate pthread_mutex_init
获取互斥 WaitForSingleObject、WaitForMultipleObjects semTake pthread_mutex_lock
释放互斥 ReleaseMutex semGive phtread_mutex_unlock
创建信号量 CreateSemaphore semBCreate、semCCreate sem_init
等待信号量 WaitForSingleObject semTake sem_wait
释放信号量 ReleaseSemaphore semGive sem_post

6.小结

本章讲述了Linux下多线程的控制及线程间通信编程方法,给出了一个生产者/消费者的实例,并将Linux的多线程与WIN32、VxWorks多线程进行了类比,总结了一般规律。鉴于多线程编程已成为开发并发应用程序的主流方法,学好本章的意义也便不言自明。

  1. #include <stdio.h>
  2. #include <stdio.h>
  3. #include <pthread.h>
  4. void thread(void)
  5. {
  6. int i;
  7. for(i=0;i<3;i++)
  8. printf("This is a pthread.\n");
  9. }
  10. int main(void)
  11. {
  12. pthread_t id;
  13. int i,ret;
  14. ret=pthread_create(&id,NULL,(void *) thread,NULL);
  15. if(ret!=0){
  16. printf ("Create pthread error!\n");
  17. exit (1);
  18. }
  19. for(i=0;i<3;i++)
  20. printf("This is the main process.\n");
  21. pthread_join(id,NULL);
  22. return (0);
  23. }

编译:

gcc example1.c -lpthread -o example1

  1. #include <pthread.h>
  2. #include <stdio.h>
  3. #include <sys/time.h>
  4. #include <string.h>
  5. #define MAX 10
  6. pthread_t thread[2];
  7. pthread_mutex_t mut;
  8. int number=0, i;
  9. void *thread1()
  10. {
  11. printf ("thread1 : I'm thread 1\n");
  12. for (i = 0; i < MAX; i++)
  13. {
  14. printf("thread1 : number = %d\n",number);
  15. pthread_mutex_lock(&mut);
  16. number++;
  17. pthread_mutex_unlock(&mut);
  18. sleep(2);
  19. }
  20. printf("thread1 :主函数在等我完成任务吗?\n");
  21. pthread_exit(NULL);
  22. }
  23. void *thread2()
  24. {
  25. printf("thread2 : I'm thread 2\n");
  26. for (i = 0; i < MAX; i++)
  27. {
  28. printf("thread2 : number = %d\n",number);
  29. pthread_mutex_lock(&mut);
  30. number++;
  31. pthread_mutex_unlock(&mut);
  32. sleep(3);
  33. }
  34. printf("thread2 :主函数在等我完成任务吗?\n");
  35. pthread_exit(NULL);
  36. }
  37. void thread_create(void)
  38. {
  39. int temp;
  40. memset(&thread, 0, sizeof(thread)); //comment1
  41. //创建线程
  42. if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2
  43. printf("线程1创建失败!\n");
  44. else
  45. printf("线程1被创建\n");
  46. if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3
  47. printf("线程2创建失败");
  48. else
  49. printf("线程2被创建\n");
  50. }
  51. void thread_wait(void)
  52. {
  53. //等待线程结束
  54. if(thread[0] !=0) { //comment4
  55. pthread_join(thread[0],NULL);
  56. printf("线程1已经结束\n");
  57. }
  58. if(thread[1] !=0) { //comment5
  59. pthread_join(thread[1],NULL);
  60. printf("线程2已经结束\n");
  61. }
  62. }
  63. int main()
  64. {
  65. //用默认属性初始化互斥锁
  66. pthread_mutex_init(&mut,NULL);
  67. printf("我是主函数哦,我正在创建线程,呵呵\n");
  68. thread_create();
  69. printf("我是主函数哦,我正在等待线程完成任务阿,呵呵\n");
  70. thread_wait();
  71. return 0;
  72. }

编译 :

gcc -lpthread -o thread_example lp.c

转自http://www.cnblogs.com/BiffoLee/archive/2011/11/18/2254540.html

互斥量、条件变量与pthread_cond_wait()函数的使用,详解(二)的更多相关文章

  1. 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解

    Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解   多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...

  2. 互斥量、条件变量与pthread_cond_wait()函数的使用,详解(一)

    1. 首先pthread_cond_wait 的定义是这样的 The pthread_cond_wait() and pthread_cond_timedwait() functions are us ...

  3. SQL Server数据库PIVOT函数的使用详解(二)

    动态的行转列 原理就是 把需要转成列的行拼出来 DECLARE @fieldName VARCHAR(); SET @fieldName=''; SELECT @fieldName = @fieldN ...

  4. C++11 多线程同步 互斥锁 条件变量

    在多线程程序中,线程同步(多个线程访问一个资源保证顺序)是一个非常重要的问题,Linux下常见的线程同步的方法有下面几种: 互斥锁 条件变量 信号量 这篇博客只介绍互斥量和条件变量的使用. 互斥锁和条 ...

  5. 节点地址的函数list_entry()原理详解

    本节中,我们继续讲解,在linux2.4内核下,如果通过一些列函数从路径名找到目标节点. 3.3.1)接下来查看chached_lookup()的代码(namei.c) [path_walk()> ...

  6. c++中内存拷贝函数(C++ memcpy)详解

    原型:void*memcpy(void*dest, const void*src,unsigned int count); 功能:由src所指内存区域复制count个字节到dest所指内存区域. 说明 ...

  7. (八)open函数的flag详解

    3.1.4.open函数的flag详解13.1.4.1.读写权限:O_RDONLY O_WRONLY O_RDWR(1)linux中文件有读写权限,我们在open打开文件时也可以附带一定的权限说明(譬 ...

  8. mysql 聚集函数 count 使用详解

    mysql 聚集函数 count 使用详解 本文将探讨以下问题 1.count(*) . count(n).count(null)与count(fieldName) 2.distinct 与 coun ...

  9. JavaScript进阶知识点——函数和对象详解

    JavaScript进阶知识点--函数和对象详解 我们在上期内容中学习了JavaScript的基本知识点,今天让我们更加深入地了解JavaScript JavaScript函数 JavaScript函 ...

随机推荐

  1. BZOJ4205卡牌配对——最大流+建图优化

    题目描述 现在有一种卡牌游戏,每张卡牌上有三个属性值:A,B,C.把卡牌分为X,Y两类,分别有n1,n2张. 两张卡牌能够配对,当且仅当,存在至多一项属性值使得两张卡牌该项属性值互质,且两张卡牌类别不 ...

  2. POJ2187-Beauty Contest-凸包

    平面最远点对 由于点数为1e5,而整数点的情况下,凸包上点的个数为sqrt(M),M为范围. 这样求出凸包之后n^2枚举维护距离就可以了 否则就用旋转卡壳. 这里用了挑战上的做法,比较简洁. #inc ...

  3. Codeforces986E Prince's Problem 【虚树】【可持久化线段树】【树状数组】

    我很喜欢这道题. 题目大意: 给出一棵带点权树.对每个询问$ u,v,x $,求$\prod_{i \in P(u,v)}gcd(ai,x)$.其中$ P(u,v) $表示$ u $到$ v $的路径 ...

  4. base64URL处理

    加密 String str="hello world"; String encode= Base64.getUrlEncoder().encodeToString(str.getb ...

  5. 【XSY2032】简单粗暴的题目 组合数

    题目描述 给你\(n,k,a_1\ldots a_n\),设 \[ ans_n=\sum_{i=1}^n{(\sum_{j=i}^ns(j))}^k\\ \] 求\(ans_1\ldots ans_n ...

  6. 2018阿里云短信发送DEMO接入简单实例

    以下更新2018-04-2309:57:54 后续不再更新, 基本类: app/SignatureHelper.php <?php namespace aliyun_mns; /** * 签名助 ...

  7. maven手动安装jar包到本地仓库,以ojdbc6为例

    在做mybatis generator的中文注释实现时,感觉每次都要在配置文件中指定ojdbc6的位置太麻烦了,别人用也不方便,没有的还得自己去下,所以就想直接把ojdbc6打包到项目里,这样拿到就可 ...

  8. mysql truncate 、delete与drop区别

    相同点: 1.truncate和不带where子句的delete.以及drop都会删除表内的数据. 2.drop.truncate都是DDL语句(数据定义语言),执行后会自动提交. 不同点: trun ...

  9. 【拓扑排序】烦人的幻灯片(slides)

    1395:烦人的幻灯片(slides) 时间限制: 1000 ms         内存限制: 65536 KB提交数: 753     通过数: 416 [题目描述] 李教授将于今天下午作一次非常重 ...

  10. python爬虫解析库学习

    一.xpath库使用: 1.基本规则: 2.将文件转为HTML对象: html = etree.parse('./test.html', etree.HTMLParser()) result = et ...