精华内容
下载资源
问答
  • 主要介绍了c语言多线程编程使用示例,需要的朋友可以参考下
  • C语言多线程编程

    千次阅读 2020-05-16 17:49:29
    线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的个...

    https://www.cnblogs.com/zzdbullet/p/9526130.html

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

    线程在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;

    pthread_create(&thrd1, NULL, (void *)&thread_function, (void *) &some_argument);

    结束线程 pthread_exit

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

    线程等待 pthread_join

    pthread_create调用成功以后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数,这个函数实际上类似与多进程编程中的waitpid。 举个例子,以下假设 A 线程调用 pthread_join 试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。 该函数包含两个参数:

    • pthread_t th //th是要等待结束的线程的标识
    • void **thread_return //指针thread_return指向的位置存放的是终止线程的返回状态。

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

    example1:

    #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创建失败\n");
        } else {
            printf("线程1创建成功\n");
        }
    
        if (ret_thrd2 != 0) {
            printf("线程2创建失败\n");
        } else {
            printf("线程2创建成功\n");
        }
    
        //同样,pthread_join的返回值成功为0
        tmp1 = pthread_join(thread1, &retval);
        printf("thread1 return value(retval) is %d\n", (int)retval);
        printf("thread1 return value(tmp) is %d\n", tmp1);
        if (tmp1 != 0) {
            printf("cannot join with thread1\n");
        }
        printf("thread1 end\n");
    
        tmp2 = pthread_join(thread1, &retval);
        printf("thread2 return value(retval) is %d\n", (int)retval);
        printf("thread2 return value(tmp) is %d\n", tmp2);
        if (tmp2 != 0) {
            printf("cannot join with thread2\n");
        }
        printf("thread2 end\n");
        return 0;
    }
    
    void print_message_function( void *ptr ) {
        int i = 0;
        for (i; i<5; i++) {
            printf("%s:%d\n", (char *)ptr, i);
        }
    }

    编译

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

    运行结果

      

     结果分析: 1.这段程序我运行了两次,可以看到,两次的运行结果是不一样的,从而说明,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度。 2.另外,我们看到,在thread2的join结果出现了错误,打印出cannot join with thread2其实这个是个小错误,因为,我pthread_join传进去的th是thread1,在上面的结果中,thread1早已经结束了,所以我们再次等待thread1结束肯定会出现无法取到状态的错误的。 3.pthread_join(thread1, &retval)确实等待了thread1的结束,我们看到,在print_message_function函数循环了5遍结束以后,才打印出thread1 end

    这是一个非常简单的例子,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)

    不加锁,数据不同步

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

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    int sharedi = 0;
    void increse_num(void);
    
    int main(){
        int ret;
        pthread_t thrd1, thrd2, thrd3;
    
        ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL);
        ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL);
        ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL);
    
        pthread_join(thrd1, NULL);
        pthread_join(thrd2, NULL);
        pthread_join(thrd3, NULL);
    
        printf("sharedi = %d\n", sharedi);
    
        return 0;
    
    }
    
    void increse_num(void) {
        long i,tmp;
        for(i=0; i<=100000; i++) {
            tmp = sharedi;
            tmp = tmp + 1;
            sharedi = tmp;
        }
    }

    编译

    gcc no_mutex.c -o nomutex -lpthread

    运行分析

    no_mutex每次的运行结果都不一致,而且,运行结果也不符合我们的预期,出现了错误的结果。 原因就是三个线程竞争访问全局变量sharedi,并且都没有进行相应的同步。

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

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

    加锁,数据同步

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

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    int sharedi = 0;
    void increse_num(void);
    
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    int main(){
        int ret;
        pthread_t thrd1, thrd2, thrd3;
    
        ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL);
        ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL);
        ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL);
    
        pthread_join(thrd1, NULL);
        pthread_join(thrd2, NULL);
        pthread_join(thrd3, NULL);
    
        printf("sharedi = %d\n", sharedi);
    
        return 0;
    
    }
    
    void increse_num(void) {
        long i,tmp;
        for(i=0; i<=100000; i++) {
        /*加锁*/
            if (pthread_mutex_lock(&mutex) != 0) {
               perror("pthread_mutex_lock");
               exit(EXIT_FAILURE);
            }
            tmp = sharedi;
            tmp = tmp + 1;
            sharedi = tmp;
        /*解锁锁*/
            if (pthread_mutex_unlock(&mutex) != 0) {
                perror("pthread_mutex_unlock");
                exit(EXIT_FAILURE);
            }
        }
    }

    结果分析

     

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

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

    2. 我们从上述运行结果中可以看到,加锁是会带来额外的开销的,加锁的代码其运行速度,明显比不加锁的要慢一些,所以,在使用锁的时候,要合理,在不需要对关键区域进行保护的场景下,我们便不要画蛇添足,为其加锁了

    方式二:信号量

    锁有一个很明显的缺点,那就是它只有两种状态:锁定与不锁定。

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

    它们是在/usr/include/semaphore.h中进行定义的,信号量的数据结构为sem_t, 本质上,它是一个long型整数

    相关函数

    在使用semaphore之前,我们需要先引入头文件#include <semaphore.h>

    • 初始化信号量: int sem_init(sem_t *sem, int pshared, unsigned int value);
      • 成功返回0,失败返回-1
      • 参数
      • sem:指向信号量结构的一个指针
      • pshared: 不是0的时候,该信号量在进程间共享,否则只能为当前进程的所有线程们共享
      • value:信号量的初始值
    • 信号量减1操作,当sem=0的时候该函数会堵塞 int sem_wait(sem_t *sem);
      • 成功返回0,失败返回-1
      • 参数
      • sem:指向信号量的一个指针
    • 信号量加1操作 int sem_post(sem_t *sem);
      • 参数与返回同上
    • 销毁信号量 int sem_destroy(sem_t *sem);
      • 参数与返回同上

    代码示例

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    #define MAXSIZE 10
    
    int stack[MAXSIZE];
    int size = 0;
    sem_t sem;
    
    // 生产者
    void provide_data(void) {
        int i;
        for (i=0; i< MAXSIZE; i++) {
            stack[i] = i;
            sem_post(&sem); //为信号量加1
        }
    }
    
    // 消费者
    void handle_data(void) {
        int i;
        while((i = size++) < MAXSIZE) {
            sem_wait(&sem);
            printf("乘法: %d X %d = %d\n", stack[i], stack[i], stack[i]*stack[i]);
            sleep(1);
        }
    }
    
    int main(void) {
    
        pthread_t provider, handler;
    
        sem_init(&sem, 0, 0); //信号量初始化
        pthread_create(&provider, NULL, (void *)handle_data, NULL);
        pthread_create(&handler, NULL, (void *)provide_data, NULL);
        pthread_join(provider, NULL);
        pthread_join(handler, NULL);
        sem_destroy(&sem); //销毁信号量
    
        return 0;
    }

    运行结果:

     

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

    展开全文
  • linxu下c语言多线程编程,同时使用到了限号量,邮箱,互斥信号量,本人亲测可以正常运行,很有参考价值。
  • C语言多线程编程,包括多线程的创建,调用,销毁,还有接口等等
  • Linux下c语言多线程编程

    千次阅读 2017-12-07 15:44:55
    转载自https://www.cnblogs.com/chenyadong/archive/2011/10/25/2223610.html Linux下c语言多线程编程

    转载自https://www.cnblogs.com/chenyadong/archive/2011/10/25/2223610.html

    Linux下c语言多线程编程

        <div class="postBody">
            <div id="cnblogs_post_body"><p>引言</p>
    

       线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多

      为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。

      使用多线程的理由之一是和进程相比,它是一种非常”节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

      使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

      除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

      1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。 
      2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。 
      3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

      下面我们先来尝试编写一个简单的多线程程序。

    简单的多线程编程

      Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序example1.c。

    /* example.c*/
    #include <stdio.h>
    #include <pthread.h>
     
    void thread(void)
    {
      int i;
      for(i=0;i<3;i++)
        printf(This is a pthread.\n);
    }
     
     int main(void)
    {
      pthread_t id;
      int i,ret;
      ret=pthread_create(&id,NULL,(void *) thread,NULL); // 成功返回0,错误返回错误编号
      if(ret!=0) {
        printf (Create pthread error!\n);
        exit (1);
      }
      for(i=0;i<3;i++)
        printf(This is the main process.\n);
      pthread_join(id,NULL);
      return (0);
    }

        我们编译此程序:
        gcc example1.c -lpthread -o example1
        运行example1,我们得到如下结果:
        This is the main process.
        This is a pthread.
        This is the main process.
        This is the main process.
        This is a pthread.
        This is a pthread.
        再次运行,我们可能得到如下结果:
        This is a pthread.
        This is the main process.
        This is a pthread.
        This is the main process.
        This is a pthread.
        This is the main process.

        前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用到了两个函数,  pthread_create和pthread_join,并声明了一个pthread_t型的变量。
        pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
        typedef unsigned long int pthread_t;
        它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:
        extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
        void *(*__start_routine) (void *), void *__arg)); 
        第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。

        函数pthread_join用来等待一个线程的结束。函数原型为:
        extern int pthread_join __P ((pthread_t __th, void **__thread_return));

        第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:
      extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

        唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。

        在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。

    修改线程的属性

      在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。

      属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

      关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的”绑”在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。

      设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。

    #include <pthread.h>
    pthread_attr_t attr; pthread_t tid; /*初始化属性值,均设为默认值*/
    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    pthread_create(&tid, &attr, (void *) my_function, NULL);

      线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

      另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。

    #include <pthread.h>
    #include <sched.h>
    pthread_attr_t attr;
    pthread_t tid;
    sched_param param;
    int newprio=20;
    pthread_attr_init(&attr);
    pthread_attr_getschedparam(&attr, &param);
    param.sched_priority=newprio;
    pthread_attr_setschedparam(&attr, &param);
    pthread_create(&tid, &attr, (void *)myfunction, myarg);
     
    线程的数据处理

      和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面,我们就逐步介绍处理线程数据时的有关知识。

        4.1 线程数据

      在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。

        和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
        创建键的函数原型为:
        extern int pthread_key_create __P ((pthread_key_t *__key,
        void (*__destr_function) (void *)));

      第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。 

      在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。

    /* 声明一个键*/
    pthread_key_t myWinKey;
     
    /* 函数 createWindow */
    void createWindow ( void )
    {
      Fl_Window * win;
      static pthread_once_t once= PTHREAD_ONCE_INIT;
      /* 调用函数createMyKey,创建键*/
      pthread_once ( &once, createMyKey) ;
      /*win指向一个新建立的窗口*/
      win=new Fl_Window( 0, 0, 100, 100, MyWindow);
      /* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
      setWindow(win);
      /* 将窗口指针值绑定在键myWinKey上*/
      pthread_setpecific ( myWinKey, win);
    }
     
    /* 函数 createMyKey,创建一个键,并指定了destructor */
    void createMyKey ( void )
    {
      pthread_keycreate(&myWinKey, freeWinKey);
    }
     
    /* 函数 freeWinKey,释放空间*/
    void freeWinKey ( Fl_Window * win)
    {
      delete win;
    }
     
      这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
          extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
          extern void *pthread_getspecific __P ((pthread_key_t __key));
      这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。
     
    4.2 互斥锁
      互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
      我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。
     
    void reader_function ( void );
    void writer_function ( void );
    char buffer;
    int buffer_has_item=0;
    pthread_mutex_t mutex;
    struct timespec delay;
    void main ( void )
    {
      pthread_t reader;
      /* 定义延迟时间*/
      delay.tv_sec = 2;
      delay.tv_nec = 0;
      /* 用默认属性初始化一个互斥锁对象*/
      pthread_mutex_init (&mutex,NULL);
      pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
      writer_function( );
    }
     
    void writer_function (void)
    {
      while(1) {
        /* 锁定互斥锁*/
        pthread_mutex_lock (&mutex);
        if (buffer_has_item==0) {
          buffer=make_new_item( );
          buffer_has_item=1;
        }
        /* 打开互斥锁*/
        pthread_mutex_unlock(&mutex);
        pthread_delay_np(&delay);
      }
    }
     
    void reader_function(void)
    {
      while(1) {
        pthread_mutex_lock(&mutex);
        if(buffer_has_item==1)
        {
          consume_item(buffer);
          buffer_has_item=0;
         }
        pthread_mutex_unlock(&mutex);
        pthread_delay_np(&delay);
      }
    }

      这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。

      pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。(如果用sleep(2)则是让进程睡眠了)

      上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

    4.3 条件变量

      前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。

        条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
        extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));

        其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。 

        函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:
        extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
        pthread_mutex_t *__mutex));

        线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。

        另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
        extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
        pthread_mutex_t *__mutex, __const struct timespec *__abstime));

        它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
        函数pthread_cond_signal()的原型为:
        extern int pthread_cond_signal __P ((pthread_cond_t *__cond));

        它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。

    pthread_mutex_t count_lock;
    pthread_cond_t count_nonzero;
    unsigned count;
    decrement_count()
    {
      pthread_mutex_lock (&count_lock);
      while(count==0)
        pthread_cond_wait( &count_nonzero, &count_lock);
      count=count -1;
      pthread_mutex_unlock (&count_lock);
    }
    increment_count()
    {
      pthread_mutex_lock(&count_lock);
      if(count==0)
        pthread_cond_signal(&count_nonzero);
      count=count+1;
      pthread_mutex_unlock(&count_lock);
    }   

      count值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。

      函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。

    4.4 信号量

      信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。

      信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为: 

      extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));

      sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。

      函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。 
      函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。 
      函数sem_destroy(sem_t *sem)用来释放信号量sem。

      下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。

    /* File sem.c */
    #include <stdio.h>
    #include <pthread.h>
    #include <semaphore.h>
    #define MAXSTACK 100
    int stack[MAXSTACK][2];
    int size=0;
    sem_t sem;
    /* 从文件1.dat读取数据,每读一次,信号量加一*/
    void ReadData1(void)
    {
      FILE *fp=fopen(1.dat,r);
      while(!feof(fp)) {
        fscanf(fp,%d %d,&stack[size][0],&stack[size][1]);
        sem_post(&sem);
        ++size;
      }
      fclose(fp);
    }
    /*从文件2.dat读取数据*/
    void ReadData2(void)
    {
      FILE *fp=fopen(2.dat,r);
      while(!feof(fp)) {
        fscanf(fp,%d %d,&stack[size][0],&stack[size][1]);
        sem_post(&sem);
        ++size;
      }
      fclose(fp);
    }
    /*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/
    void HandleData1(void)
    {
      while(1) {
        sem_wait(&sem);
        printf(Plus:%d+%d=%d\n,stack[size][0],stack[size][1], stack[size][0]+stack[size][1]);
        –size;
      }
    }
    void HandleData2(void)
    {
      while(1) {
        sem_wait(&sem);
        printf(Multiply:%d*%d=%d\n,stack[size][0],stack[size][1], stack[size][0]*stack[size][1]);
        –size;
      }
    }
    int main(void)
    {
      pthread_t t1,t2,t3,t4;
      sem_init(&sem,0,0);
      pthread_create(&t1,NULL,(void *)HandleData1,NULL);
      pthread_create(&t2,NULL,(void *)HandleData2,NULL);
      pthread_create(&t3,NULL,(void *)ReadData1,NULL);
      pthread_create(&t4,NULL,(void *)ReadData2,NULL);
      /* 防止程序过早退出,让它在此无限期等待*/
      pthread_join(t1,NULL);
    }

      在Linux下,我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem。 我们事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我们运行sem,得到如下的结果:
        Multiply:-1*-2=2
        Plus:-1+-2=-3
        Multiply:9*10=90
        Plus:-9+-10=-19
        Multiply:-7*-8=56
        Plus:-5+-6=-11
        Multiply:-3*-4=12
        Plus:9+10=19
        Plus:7+8=15
        Plus:5+6=11 

      从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这也往往是多线程编程要注意的问题。 

     小结 

       多线程编程是一个很有意思也很有用的技术,使用多线程技术的网络蚂蚁是目前最常用的下载工具之一,使用多线程技术的grep比单线程的grep要快上几倍,类似的例子还有很多。希望大家能用多线程技术写出高效实用的好程序来。

    展开全文
  • linux下C语言多线程编程实例,是个基本的实例,很简单
  • Linux中用C语言多线程编程之pthread_join()函数 在Ubuntu Linux上用C语言进行多线程编程 一运行就报错 报错如下: 段错误 (核心已转储) 经过多次调试 发现是因为创建了多线程但没有进行多个线程之间的连接 而对多...

    Linux中用C语言多线程编程之pthread_join()函数

    在Ubuntu Linux上用C语言进行多线程编程

    一运行就报错

    报错如下:

    段错误 (核心已转储)
    

    在这里插入图片描述
    经过多次调试

    发现是因为创建了多线程但没有进行多个线程之间的连接

    而对多线程进行连接要用到pthread_join()函数

    下面学习学习pthread_join()函数

    • pthread_join()函数 函数pthread_join用来等待一个线程的结束,线程间同步、连接的操作。
      头文件 : #include <pthread.h>
      函数定义: int pthread_join(pthread_t thread, void **retval);
      描述 : pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
      参数 : thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
      返回值 : 0代表成功。 失败,返回的则是错误号。

    在Linux中,默认情况下是在一个线程被创建后,必须使用此函数对创建的线程进行资源回收,但是可以设置Threads attributes来设置当一个线程结束时,直接回收此线程所占用的系统资源。

    其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统调用copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。 copy后的进程和原先的进程共享了所有的变量,运行环境。这样,原先进程中的变量变动在copy后的进程中便能体现出来。

    pthread_join作用就是使一个线程等待另一个线程结束。

    代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

    所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。

    通过调用pthread_self()函数可以获得自身的线程号。

    所以,进行多线程编程时

    创建了多个线程

    应该使用pthread_join()函数将各线程进行连接

    通常,pthread_join()函数会放在一个for循环中进行多线程之间的连接

    相关技术文章:
    【Linux问题解决】Ubuntu Linux 安装gcc4.9 g++4.9报错“没有可供安装的候选者”解决办法
    【Linux教程】Ubuntu Linux 更换源教程
    【Linux教程】如何实现在Ubuntu Linux和windows之间复制粘贴、拖拽复制文件?
    【Linux问题解决】操作系统用C语言多线程编程 对‘pthread_create’未定义的引用 报错解决办法

    【Java Web总结】Java Web项目中 的.classpath、.mymetadata、.project文件作用
    【Java Web问题解决】Tomcat报错javax.servlet.ServletException: Error instantiating servlet class.报错404
    【比较】什么是“服务器端跳转”“客户端跳转”,二者有什么区别?
    【总结】表单提交的get和post有什么不同?
    【总结】Java Web 中的4种属性范围(page、request、session、application)
    【Java Web问题解决】Tomcat启动时控制台出现中文乱码的问题解决方法
    【示例项目】java实现通过身份证号码判断籍贯所在地区
    【总结】HTTP协议中的状态码(200、403、404、500等)
    【Java Web问题解决】提交表单后显示乱码
    【Java Web总结】JSP页面的生命周期详解
    【Java Web总结】JSP页面实现类详解
    【Java Web 问题解决】Tomcat启动失败 报错:Server Tomcat v9.0 Server at localhost failed to start.
    【Java Web问题解决】连接数据库出错:java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/

    展开全文
  • 简单实现linux下c语言多线程编程,代码很简单,如下所示: /************************************************************************* &gt; File Name: module.c &gt; Author: xiao5 &gt; ...

    简单实现linux下c语言多线程编程,代码很简单,如下所示:

    /*************************************************************************
    	> File Name: module.c
    	> Author: xiao5
    	> Mail: xiao5_zju@163.com
    	> Created Time: 2016年02月03日 星期三 10时33分26秒
        > Notes: pthread不是Linux的标准库,编译时要家-lpthread参数
     ************************************************************************/
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<pthread.h>
    
    #define THREAD_NUM 10       // 定义线程的数目
    /* 线程需要执行的函数 */
    void *test(void *args)
    {
        printf("Thread ID %d: say 'Hello'.\n", args);
        return NULL;
    }
    
    int main()
    {
        int i, err;
        pthread_t child[THREAD_NUM];        // 线程标识符函数
    
        for(i=0;i<THREAD_NUM;i++)
        {
            printf("Creating thread %d\n", i);
            /*
            * pthread_t *thread: 线程标识符 
            * pthread_attr_t *attr: 线程属性设置
            * void *(*start_routine)(void*): 线程函数的起始地址
            * void *args: 传递给线程函数的参数
            */
            err = pthread_create(&child[i], NULL, test, (void*)i);  // 线程创建函数
            if(err)
            {
                printf("Can't create thread %d\n", i);
                exit(0);
            }
        }
        /* 等待线程结束函数 */
        for(i=0;i<THREAD_NUM;i++)
        {
            pthread_join(child[i], NULL);
        }
        printf("Thread initialize\n");
        return 0;
    }
    

     运行结果如下:


    可以发现线程的异步运行!

    展开全文
  • C语言多线程编程基础

    千次阅读 2016-04-13 17:33:37
    我们进行多线程编程,可以有多种选择,可以使用WindowsAPI,如果你在使用GTK,也可以使用GTK实现了的线程库,如果你想让你的程序有更多的移植性你最好是选择POSIX中的Pthread函数库,我的程序是在Linux下写的,所以...
  • 线程如何去创建 ,多线程如何控制输出!互斥控制!
  • C语言多线程编程-线程的基本函数

    万次阅读 多人点赞 2017-04-06 22:33:21
    线程操作函数,线程属性控制函数
  • 操作系统用C语言多线程编程 对‘pthread_create’未定义的引用 报错解决办法 今天写操作系统作业 在Ubuntu Linux系统中用C语言编写多线程程序 在命令行进行编译 没通过编译 报错如下: In file included from 4.15.c...
  • Linux下C语言多线程编程

    千次阅读 2014-07-18 09:50:35
    一、多线程的创建 1、Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连 接时需要使用库libpthread.a。因此,后面的编译必须在选项中加入 -lpthread...
  • windows下C语言多线程编程

    万次阅读 2016-06-03 11:36:02
    _beginThreadex创建多线程解读 一、需要的头文件支持  #include // for _beginthread() 需要的设置:ProjectàSetting-->C/C++-->User run-time library 选择Debug Multithreaded 或者Multithreaded。...
  • C语言多线程编程-进程和线程的基本概念

    万次阅读 多人点赞 2019-04-04 00:29:40
    实验室项目采用多线程实现,然而暂时只涉及到几个基本的线程操作函数,线程和进程的区别、线程的同步和异步机制以及线程通信等暂时都没有涉及,打算在这里做些总结,以备后用。 1. 进程 是一种抽象的概念,从来没有...
  • 最近学习c语言和linux,记录一下linux中线程的简单使用: linux线程一般用pthread库创建。pthread是 POSIX thread的简称。 在Linux的/lib目录下,可以找到名为libpthread-x.x.so(x.x是版本号)的库。下面模拟两...
  • 介绍Linux下多线程编程的博客:https://www.cnblogs.com/nfcm/p/7653433.html pthread_create传递带参数的函数:https://blog.csdn.net/computerme/article/details/52421928 结合这些知识,自己写了了一个demo,...
  • windows下C语言多线程编程Demo

    千次阅读 2013-10-13 18:52:35
    #include #include #include void ChildThread(void* param) { int *ele=(int*)param; printf("%d\t",*ele); _endthread(); } int main() { int i; int data[5]={1,2,3,4,5}; for(i=0;... _be
  • 线程同步,互斥锁,条件变量,读写锁

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 70,015
精华内容 28,006
关键字:

c语言多线程编程

c语言 订阅