Linux下线程同步互斥

来源:互联网 发布:mac版软件下载大全 编辑:程序博客网 时间:2024/05/14 17:29

线程的基本函数

大多数pthread_XXX系列的函数在失败时,并未遵循UNIX函数的惯例返回-1,这种情况在UNIX函数中属于一少部分。如果调用成功,则返回值是0,如果失败则返回错误代码。

 

1.线程创建:

#include <pthread.h>

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

参数说明:

thread:指向pthread_create类型的指针,用于引用新创建的线程。

attr:用于设置线程的属性,一般不需要特殊的属性,所以可以简单地设置为NULL。

*(*start_routine)(void *):传递新线程所要执行的函数地址。

arg:新线程所要执行的函数的参数。

调用如果成功,则返回值是0,如果失败则返回错误代码。

 

2.线程终止

#include <pthread.h>

void pthread_exit(void *retval);

参数说明:

retval:返回指针,指向线程向要返回的某个对象。

线程通过调用pthread_exit函数终止执行,并返回一个指向某对象的指针。注意:绝不能用它返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量就不存在了,这将引起严重的程序漏洞。

 

3.线程同步

#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

参数说明:

th:将要等待的张璐,线程通过pthread_create返回的标识符来指定。

thread_return:一个指针,指向另一个指针,而后者指向线程的返回值。

 

一个简单的多线程Demo(thread1.c):

[cpp] view plain copy
  1. #include <pthread.h>  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5.   
  6. void *thread_function(void *arg);  
  7.   
  8. char message[] = "Hello World";  
  9.   
  10. int main()  
  11. {  
  12.     int res;  
  13.     pthread_t a_thread;  
  14.     void *thread_result;  
  15.   
  16.     res = pthread_create(&a_thread, NULL, thread_function, (void *)message);  
  17.     if (res != 0)  
  18.     {  
  19.         perror("Thread creation failed!");  
  20.         exit(EXIT_FAILURE);  
  21.     }  
  22.   
  23.     printf("Waiting for thread to finish.../n");  
  24.       
  25.     res = pthread_join(a_thread, &thread_result);  
  26.     if (res != 0)  
  27.     {  
  28.         perror("Thread join failed!/n");  
  29.         exit(EXIT_FAILURE);  
  30.     }  
  31.   
  32.     printf("Thread joined, it returned %s/n", (char *)thread_result);  
  33.     printf("Message is now %s/n", message);  
  34.   
  35.     exit(EXIT_FAILURE);  
  36. }  
  37.   
  38. void *thread_function(void *arg)  
  39. {  
  40.     printf("thread_function is running. Argument was %s/n", (char *)arg);  
  41.     sleep(3);  
  42.     strcpy(message, "Bye!");  
  43.     pthread_exit("Thank you for your CPU time!");  
  44. }  

 

编译这个程序时,需要定义宏_REENTRANT:

gcc -D_REENTRANT thread1.c -o thread1 –lpthread

 

运行这个程序:

$ ./thread1输出:

thread_function is running. Argument was Hello World

Waiting for thread to finish...

Thread joined, it returned Thank you for your CPU time!

Message is now Bye!

 

这个例子值得我们去花时间理解,因为它将作为几个例子的基础。

 

pthread_exit(void *retval)本身返回的就是指向某个对象的指针,因此,pthread_join(pthread_t th, void **thread_return);中的thread_return是二级指针,指向线程返回值的指针。

可以看到,我们创建的新线程修改的数组message的值,而原先的线程也可以访问该数组。如果我们调用的是fork而不是pthread_create,就不会有这样的效果了。原因是fork创建子进程之后,子进程会拷贝父进程,两者分离,相互不干扰,而线程之间则是共享进程的相关资源。

 

线程的同时执行

接下来,我们来编写一个程序,以验证两个线程的执行是同时进行的。当然,如果是在一个单处理器系统上,线程的同时执行就需要靠CPU在线程之间的快速切换来实现了。

我们的程序需要利用一个原理:即除了局部变量外,所有其他的变量在一个进程中的所有线程之间是共享的。

在这个程序中,我们是在两个线程之间使用轮询技术,这种方式称为忙等待,所以它的效率会很低。在本文的后续部分,我们将介绍一种更好的解决办法。

 

       下面的代码中,两个线程会不断的轮询判断flag的值是否满足各自的要求。

[cpp] view plain copy
  1. #include <pthread.h>  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4.   
  5. int flag = 1;  
  6.   
  7. void *thread_function(void *arg);  
  8.   
  9. int main()  
  10. {  
  11.     int res;  
  12.     pthread_t a_thread;  
  13.     void *thread_result;  
  14.     int count = 1;  
  15.   
  16.     res = pthread_create(&a_thread, NULL, thread_function, NULL);  
  17.     if (res != 0)  
  18.     {  
  19.         perror("Thread creation failed");  
  20.         exit(EXIT_FAILURE);  
  21.     }  
  22.   
  23.     while (count++ <= 20)  
  24.     {  
  25.         if (flag == 1)  
  26.         {  
  27.             printf ("1");  
  28.             flag = 2;  
  29.         }  
  30.         else  
  31.         {  
  32.             sleep(1);  
  33.         }  
  34.     }  
  35.   
  36.     printf("/nWaiting for thread to finish.../n");  
  37.     res = pthread_join(a_thread, &thread_result);  
  38.     if (res != 0)  
  39.     {  
  40.         perror("Thread join failed");  
  41.         exit(EXIT_FAILURE);  
  42.     }  
  43.   
  44.     exit(EXIT_SUCCESS);  
  45. }  
  46.   
  47. void *thread_function(void *arg)  
  48. {  
  49.     int count = 1;  
  50.   
  51.     while (count++ <= 20)  
  52.     {  
  53.         if (flag == 2)  
  54.         {  
  55.             printf("2");  
  56.             flag = 1;  
  57.         }  
  58.         else  
  59.         {  
  60.             sleep(1);  
  61.         }  
  62.     }  
  63. }  

 

编译这个程序:

gcc -D_REENTRANT thread2.c -o thread2 –lpthread

 

运行这个程序:

$ ./thread2

121212121212121212

Waiting for thread to finish...

 

.线程的同步

在上述示例中,我们采用轮询的方式在两个线程之间不停地切换是非常笨拙且没有效率的实现方式,幸运的是,专门有一级设计好的函数为我们提供更好的控制线程执行和访问代码临界区的方法。

本小节将介绍两个线程同步的基本方法:信号量和互斥量。这两种方法很相似,事实上,它们可以互相通过对方来实现。但在实际的应用中,对于一些情况,可能使用信号量或互斥量中的一个更符合问题的语义,并且效果更好。

 

5.1用信号量进行同步

1.信号量创建

#include <semaphore.h>

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

参数说明:

sem:信号量对象。

pshared:控制信号量的类型,0表示这个信号量是当前进程的局部信号量,否则,这个信号量就可以在多个进程之间共享。

value:信号量的初始值。

 

2.信号量控制

#include <semaphore.h>

int sem_wait(sem_t *sem);

int sem_post(sem_t *sem);

 

sem_post的作用是以原子操作的方式给信号量的值加1。
       sem_wait的作用是以原子操作的方式给信号量的值减1,但它会等到信号量非0时才会开始减法操作。如果对值为0的信号量调用sem_wait,这个函数就会等待,直到有线程增加了该信号量的值使其不再为0。

 

3.信号量销毁

#include <semaphore.h>

int sem_destory(sem_t *sem);

这个函数的作用是,用完信号量后对它进行清理,清理该信号量所拥有的资源。如果你试图清理的信号量正被一些线程等待,就会收到一个错误。

与大多数Linux函数一样,这些函数在成功时都返回0。

 

下面编码实现输入字符串,统计每行的字符个数,以“end”结束输入:

[cpp] view plain copy
  1. #include <pthread.h>  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5. #include <semaphore.h>  
  6.   
  7. #define SIZE 1024  
  8.   
  9. void *thread_function(void *arg);  
  10.   
  11. char buffer[SIZE];  
  12. sem_t sem;  
  13.   
  14. int main()  
  15. {  
  16.     int res;  
  17.     pthread_t a_thread;  
  18.     void *thread_result;  
  19.   
  20.     res = sem_init(&sem, 0, 0);  
  21.     if (res != 0)  
  22.     {  
  23.         perror("Sem init failed");  
  24.         exit(EXIT_FAILURE);  
  25.     }  
  26.   
  27.     res = pthread_create(&a_thread, NULL, thread_function, NULL);  
  28.     if (res != 0)  
  29.     {  
  30.         perror("Thread create failed");  
  31.         exit(EXIT_FAILURE);  
  32.     }  
  33.   
  34.     printf("Input some text. Enter 'end' to finish/n");  
  35.   
  36.     while (scanf("%s", buffer))  
  37.     {  
  38.         sem_post(&sem);  
  39.         if (strncmp("end", buffer, 3) == 0)  
  40.             break;  
  41.     }  
  42.   
  43.     printf ("/nWaiting for thread to finish.../n");  
  44.   
  45.     res = pthread_join(a_thread, &thread_result);  
  46.     if (res != 0)  
  47.     {  
  48.         perror("Thread join failed");  
  49.         exit(EXIT_FAILURE);  
  50.     }  
  51.   
  52.     printf ("Thread join/n");  
  53.   
  54.     sem_destroy(&sem);  
  55.   
  56.     exit(EXIT_SUCCESS);  
  57. }  
  58.   
  59. void *thread_function(void *arg)  
  60. {  
  61.     sem_wait(&sem);  
  62.     while (strncmp("end", buffer, 3) != 0)  
  63.     {  
  64.         printf("You input %d characters/n", strlen(buffer));  
  65.         sem_wait(&sem);  
  66.     }  
  67.     pthread_exit(NULL);  
  68. }  

 

编译这个程序:

gcc -D_REENTRANT thread2.c -o thread2 –lpthread

 

运行这个程序:

$ ./thread3

Input some text. Enter 'end' to finish

123

You input 3 characters

1234

You input 4 characters

12345

You input 5 characters

end

 

Waiting for thread to finish…

Thread join

 

       通过使用信号量,我们阻塞了统计字符个数的线程,这个程序似乎对快速的文本输入和悠闲的暂停都很适用,比之前的轮询解决方案效率上有了本质的提高。

 

用互斥量进行线程同步

       另一种用在多线程程序中同步访问的方法是使用互斥量。它允许程序员锁住某个对象,使得每次只能有一个线程访问它。为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁它。

       用于互斥量的基本函数和用于信号量的函数非常相似:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t, *mutexattr);

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_destory(pthread_mutex_t *mutex);

与其他函数一样,成功时返回0,失败时将返回错误代码,但这些函数并不设置errno,所以必须对函数的返回代码进行检查。互斥量的属性设置这里不讨论,因此设置成NULL。

我们用互斥量来重写刚才的代码如下:

[cpp] view plain copy
  1. #include <pthread.h>  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5. #include <semaphore.h>  
  6.   
  7. #define SIZE 1024  
  8. char buffer[SIZE];  
  9.   
  10. void *thread_function(void *arg);  
  11. pthread_mutex_t mutex;  
  12.   
  13. int main()  
  14. {  
  15.     int res;  
  16.     pthread_t a_thread;  
  17.     void *thread_result;  
  18.   
  19.     res = pthread_mutex_init(&mutex, NULL);  
  20.     if (res != 0)  
  21.     {  
  22.         perror("Mutex init failed!");  
  23.         exit(EXIT_FAILURE);  
  24.     }  
  25.   
  26.     res = pthread_create(&a_thread, NULL, thread_function, NULL);  
  27.     if (res != 0)  
  28.     {  
  29.         perror("Thread create failed!");  
  30.         exit(EXIT_FAILURE);  
  31.     }  
  32.   
  33.     printf("Input some text. Enter 'end' to finish/n");  
  34.   
  35.     while (1)  
  36.     {  
  37.         pthread_mutex_lock(&mutex);  
  38.         scanf("%s", buffer);  
  39.         pthread_mutex_unlock(&mutex);  
  40.         if (strncmp("end", buffer, 3) == 0)  
  41.             break;  
  42.         sleep(1);  
  43.     }  
  44.   
  45.     res = pthread_join(a_thread, &thread_result);  
  46.     if (res != 0)  
  47.     {  
  48.         perror("Thread join failed!");  
  49.         exit(EXIT_FAILURE);  
  50.     }  
  51.   
  52.     printf("Thread joined/n");  
  53.   
  54.     pthread_mutex_destroy(&mutex);  
  55.   
  56.     exit(EXIT_SUCCESS);  
  57. }  
  58.   
  59. void *thread_function(void *arg)  
  60. {  
  61.     sleep(1);  
  62.   
  63.     while (1)  
  64.     {  
  65.         pthread_mutex_lock(&mutex);  
  66.         printf("You input %d characters/n", strlen(buffer));  
  67.         pthread_mutex_unlock(&mutex);  
  68.         if (strncmp("end", buffer, 3) == 0)  
  69.             break;  
  70.         sleep(1);  
  71.     }  
  72. }  

 

编译这个程序:

gcc -D_REENTRANT thread4.c -o thread4 –lpthread

 

运行这个程序:

$ ./thread4

Input some text. Enter 'end' to finish

123

You input 3 characters

1234

You input 4 characters

12345

You input 5 characters

end

You input 3 characters

Thread joined

0 0