zoukankan      html  css  js  c++  java
  • linux C多线程编程入门(基本API及多线程的同步与互斥)

    介绍:什么是线程,线程的优点是什么?

    线程在unix下,被称为轻量级的进程,线程虽然不是进程,但却可以看作是unix进程的表亲,同一进程中的多条线程将共享该进程中的全部资源,如虚拟地址空间,文件描述服,和信号处理等等。但同一进程中的多个线程有各自的调用占(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。一个进程可以有很多线程,每条线程执行不同的任务。

    线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O 等会产生堵塞的情况的表现性能。在unix系统中,一个进程包含很多东西,包括可执行的程序,以及一大堆诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进频繁的切换,开销很多大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得高效非常。

    hello world(线程创建 , 结束, 等待)

    创建线程:pthread_create()

    线程创建函数包含四个变量:1、一个线程变量名,被创建线程的标识。2、线程的属性指针,缺省为NULL即可。3、被创建线程的程序代码 4、程序的代码的参数:for example :

    -pthread_t thrd1;-pthread_attr_t attr; -void thread_function(void argument);-char *some_argument;

    1
    2
    int pthread_create(pthread_t *thread,pthread_attr_t *attr ,void *(*func), void *arg);
    返回值 0 成功 ,返回 errcode 失败

    结束线程:pthread_exit()

    线程结束调用pthread_exit(void * retval);//retval用于存放进程结束的退出状态。

    线程等待:pthread_join()

    pthread_create()调用成功后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决于操作系统对县城的调度,如果我们需要等待指定线程结束,需要使用pthread_join()函数,这个函数实际上类似于多进程编程中的waitpid。举例:假设A线程调用pthread_join

    试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。该函数:

    int pthread_join(pthread_t thread , void ** retval);

    参数:thread所等待的进程,retval指向某个存储线程返回值的变量

    返回值: 0 成功,返回 errcode 错误。

    调用示例: pthread_join(thrd1,NULL);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    /*************************************************************************
        > File Name: thread_hello_world.c 
        > Author: couldtt(fyby)
        > Mail:  fuyunbiyi@gmail.com
        > Created Time: 2013年12月14日 星期六 11时48分50秒
     ************************************************************************/
     
    #include <stdio.h> 
    #include <stdlib.h> 
    #include <pthread.h> 
     
    void print_message_function (void *ptr); 
     
    int main() 
        int tmp1, tmp2; 
        void *retval; 
        pthread_t thread1, thread2; 
        char *message1 = "thread1"
        char *message2 = "thread2"
     
        int ret_thrd1, ret_thrd2; 
     
        ret_thrd1 = pthread_create(&thread1, NULL, (void *)&print_message_function, (void *) message1); 
        ret_thrd2 = pthread_create(&thread2, NULL, (void *)&print_message_function, (void *) message2); 
     
        // 线程创建成功,返回0,失败返回失败号 
        if (ret_thrd1 != 0) { 
            printf("线程1创建失败 "); 
        else 
            printf("线程1创建成功 "); 
        
     
        if (ret_thrd2 != 0) { 
            printf("线程2创建失败 "); 
        else 
            printf("线程2创建成功 "); 
        
     
        //同样,pthread_join的返回值成功为0 
        tmp1 = pthread_join(thread1, &retval); 
        printf("thread1 return value(retval) is %d ", (int)retval); 
        printf("thread1 return value(tmp) is %d ", tmp1); 
        if (tmp1 != 0) { 
            printf("cannot join with thread1 "); 
        
        printf("thread1 end "); 
     
        tmp2 = pthread_join(thread1, &retval); 
        printf("thread2 return value(retval) is %d ", (int)retval); 
        printf("thread2 return value(tmp) is %d ", tmp1); 
        if (tmp2 != 0) { 
            printf("cannot join with thread2 "); 
        
        printf("thread2 end "); 
     
     
    void print_message_function( void *ptr ) { 
        int i = 0
        for (i; i<5; i++) { 
            printf("%s:%d ", (char *)ptr, i); 
        
    }

    编译:

    gcc thread_hello_world.c -o test -lpthread 注意:一定要加上-lpthread,要不然会报错,因为源代码里引用了pthread.H里的东西,所以在gcc进行连接的时候,必须要找到这些库的二进制实现代码。

    结果分析:

    1、这段代码我运行了两次,可以看到,两次运行的结果是不一样的,从而说明,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决于操作系统的调度。

    2、这是一个非常简单的例子,hello world 级别的。只使用来掩饰下linux 下 C多线程的使用,在实际应用中由于多线程往往会访问共享的资源(典型的是访问同一个全局变量),因此多个线程间存在着竞争的关系,这就需要对多个线程进行同步,对其访问的数据予以保护。

    多线程的同步与互斥:

     

    方式一:锁

        在主线程中初始化锁为解锁状态

            pthread_mutex_t mutex;

            pthread_mutex_init(&mutex,NULL);

        在编译时初始化锁为解锁状态:

            锁初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

        访问对象时的加锁操作与解锁操作

            加锁 pthread_mutex_lock(&mutex);

            释放锁 pthread_mutex_unlock(&mutex);

    不加锁数据不同步

    我们先来看一个不加锁,多个线程访问同一段数据的程序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    1 /*************************************************************************
     2     > File Name: no_mutex.c
     3     > Author: couldtt(fyby)
     4     > Mail: fuyunbiyi@gmail.com
     5     > Created Time: 2013年12月15日 星期日 17时52分24秒
     6  ************************************************************************/
     7
     8 #include <stdio.h>
     9 #include <stdlib.h>
    10 #include <pthread.h>
    11
    12 int sharedi = 0;
    13 void increse_num(void);
    14
    15 int main(){
    16     int ret;
    17     pthread_t thrd1, thrd2, thrd3;
    18
    19     ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL);
    20     ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL);
    21     ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL);
    22
    23     pthread_join(thrd1, NULL);
    24     pthread_join(thrd2, NULL);
    25     pthread_join(thrd3, NULL);
    26
    27     printf("sharedi = %d ", sharedi);
    28
    29     return 0;
    30
    31 }
    32
    33 void increse_num(void) {
    34     long i,tmp;
    35     for(i=0; i<=100000; i++) {
    36         tmp = sharedi;
    37         tmp = tmp + 1;
    38         sharedi = tmp;
    39     }
    40 }

    编译运行之后,(不加锁),我们可以知道,每次运行的结果都会不一样,而其运行结果也不符合我们的预期,出现了错误的结果。原因是三个线程竞争访问全局变量sharendi,并且都没有进行相应的同步。

    举个例子:当线程thrd1访问到 sharendi的时候,sharendi的值是1000,然后线程thrd1将sharendi的值累加到了1001,可是线程thrd2取得sharendi的时候,sharendi的值是1000,这时候线程thrd2对sharendi的值进行加1的操作,变成了1001,可是这个时候,sharendi的值已经被线程thrd1加到1001了,然而,thrd2并不知情1,所以又将sharendi的值赋值为了1001,从而导致了结果的错误。

        这样来看,我们就需要一个线程互斥的机制来保护sharendi这个变量,让同一时刻,只有一个线程能够访问到这个变量,从而使它的值能够保证正确的变化。

    加锁,数据同步

    通过枷锁,保证sharendi变量在进行变更的时候,只有一个线程能够收到,并在该线程对其进行操作的时候,其它线程无法对其进行访问。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    1 /*************************************************************************
     2     > File Name: mutex.c
     3     > Author: couldtt(fyby)
     4     > Mail: fuyunbiyi@gmail.com
     5     > Created Time: 2013年12月15日 星期日 17时52分24秒
     6  ************************************************************************/
     7
     8 #include <stdio.h>
     9 #include <stdlib.h>
    10 #include <pthread.h>
    11
    12 int sharedi = 0;
    13 void increse_num(void);
    14
    15 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    16
    17 int main(){
    18     int ret;
    19     pthread_t thrd1, thrd2, thrd3;
    20
    21     ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL);
    22     ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL);
    23     ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL);
    24
    25     pthread_join(thrd1, NULL);
    26     pthread_join(thrd2, NULL);
    27     pthread_join(thrd3, NULL);
    28
    29     printf("sharedi = %d ", sharedi);
    30
    31     return 0;
    32
    33 }
    34
    35 void increse_num(void) {
    36     long i,tmp;
    37     for(i=0; i<=100000; i++) {
    38     /*加锁*/
    39         if (pthread_mutex_lock(&mutex) != 0) {
    40            perror("pthread_mutex_lock");
    41            exit(EXIT_FAILURE);
    42         }
    43         tmp = sharedi;
    44         tmp = tmp + 1;
    45         sharedi = tmp;
    46     /*解锁锁*/
    47         if (pthread_mutex_unlock(&mutex) != 0) {
    48             perror("pthread_mutex_unlock");
    49             exit(EXIT_FAILURE);
    50         }
    51     }
    52 }

    结果分析:加锁

    这一次我们是正确的,锁有效的的保护了我们的数据安全,然而:

    1、锁保护的并不是我们的共享变量(或者是说共享内存),对于共享的内存而言,用户是无法直接对其保护的,因为那是物理内存,无法阻止其它程序的代码访问。事实上,锁所以对关键区域进行了保护,在本例中,是因为所有的线程都遵循一个原则,那就是在进入关键区域前加同一把锁,再退出关键区域前释放同一把锁。

    2、加锁时会带来额外开销的,加锁的代码去运行速度不如不加锁的快,所以,在使用锁的时候,要合理,再不需要对关键区域进行保护的场景下,我们便不要画蛇添足,为其加锁了。

    方式二:信号量

    所有一个横明显的缺点就是他只有两种状态:锁定,不锁定

    信号量本质上是一个非负数的整数计数器,他也被用来控制对公共资源的访问。当公共资源增加的时候,调用信号量增加函数sem_post()对其进行增加,当公共资源减少的时候,调用函数sem_wait()来减少信号量。其实,我们是可以把锁当作一个0-1信号量的。

    它是在semaphore.h中定义的,信号量的数据结构为sem_t ,本质上,他是一个long型整数。

    相关函数:

        初始化信号量: int sem_inin(sem_t *sem,int pshared, unsigned int value);

            成功返回0,失败返回-1

            参数:

            sem:指向信号量结构的一个指针

            pshared:不是0 的时候,该信号量在线程间共享,否则只能为当前进程的所有线程们共享。

            value:信号量的初始值

        信号量减一操作,当sem = 0的时候该函数会堵塞 int sem_wait(sem_t * sem);

            成功返回0 ,失败返回-1

            参数:

            sem:指向信号量的一个指针

        信号量加一操作:int   sem_post(sem_t * sem);

            参数与返回同上

        销毁信号量:=: int sem_destroy(sem_t * sem);

            参数与返回同上

    代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    1 /*************************************************************************
     2     > File Name: sem.c
     3     > Author: couldtt(fyby)
     4     > Mail: fuyunbiyi@gmail.com
     5     > Created Time: 2013年12月15日 星期日 19时25分08秒
     6  ************************************************************************/
     7
     8 #include <stdio.h>
     9 #include <unistd.h>
    10 #include <pthread.h>
    11 #include <semaphore.h>
    12
    13 #define MAXSIZE 10
    14
    15 int stack[MAXSIZE];
    16 int size = 0;
    17 sem_t sem;
    18
    19 // 生产者
    20 void provide_data(void) {
    21     int i;
    22     for (i=0; i< MAXSIZE; i++) {
    23         stack[i] = i;
    24         sem_post(&sem); //为信号量加1
    25     }
    26 }
    27
    28 // 消费者
    29 void handle_data(void) {
    30     int i;
    31     while((i = size++) < MAXSIZE) {
    32         sem_wait(&sem);
    33         printf("乘法: %d X %d = %d ", stack[i], stack[i], stack[i]*stack[i]);
    34         sleep(1);
    35     }
    36 }
    37
    38 int main(void) {
    39
    40     pthread_t provider, handler;
    41
    42     sem_init(&sem, 00); //信号量初始化
    43     pthread_create(&provider, NULL, (void *)handle_data, NULL);
    44     pthread_create(&handler, NULL, (void *)provide_data, NULL);
    45     pthread_join(provider, NULL);
    46     pthread_join(handler, NULL);
    47     sem_destroy(&sem); //销毁信号量
    48
    49     return 0;
    50 }

    运行结果:

    信号量的使用,因为信号量机制的存在,所以代码在handle_data的时候,如果sem_wait(&sem);时,sem为0 ,那么代码会堵塞在sem_wait()上面,从而避免了在stack中访问错误的index而使真个程序崩溃。

  • 相关阅读:
    三位数
    顺序表应用4:元素位置互换之逆置算法
    顺序表应用5:有序顺序表归并
    顺序表应用6:有序顺序表查询
    数据结构实验之图论八:欧拉回路
    串结构练习——字符串连接 Description
    图的基本存储的基本方式三
    数据结构实验之图论四:迷宫探索
    数据结构实验之图论二:图的深度遍历
    图的基本存储的基本方式二
  • 原文地址:https://www.cnblogs.com/yjds/p/8598874.html
Copyright © 2011-2022 走看看