精华内容
下载资源
问答
  • POSIX线程优先级设置

    2021-03-24 14:14:13
    POSIX线程优先级的修改方式有静态修改、动态修改。 静态修改:在线程启动前设置初始值,即修改线程属性块的值。 动态修改:在线程启动后动态修改,通过posix调度函数进行动态修改。 静态修改: 在调用 pthread_...

    POSIX线程优先级的修改方式有静态修改、动态修改。

    • 静态修改:在线程启动前设置初始值,即修改线程属性块的值。
    • 动态修改:在线程启动后动态修改,通过posix调度函数进行动态修改。

    静态修改:

    在调用 pthread_create 函数创建启动线程之前,调用 pthread_attr_init 函数初始化一个线程属性对象

    #include <pthread.h>
    int pthread_attr_init(pthread_attr_t *pattr);

    同时posix提供了一系列函数用于 获取/设置 线程属性对象各成员的值,函数的 pthread_attr_getschedparam 输入参数 pschedparam 结构体中含有存储优先级的成员变量,调用 pthread_attr_setschedparam 函数可以设置线程属性对象的调度参数,调用 pthread_attr_getschedparam 函数将获得线程属性对象的调度参数。函数定义如下图:

    #include <pthread.h>
    int pthread_attr_setschedparam(pthread_attr_t *pattr, const struct sched_param *pschedparam);
    int pthread_attr_getschedparam(const pthread_attr_t *pattr,  struct sched_param *pschedparam);

     

    动态修改:

    posix提供了一系列线程调度函数用于动态修改线程属性,其中 sched_setscheduler 函数提供了一种动态改变线程优先级的方法。调用 sched_setscheduler 函数在设置调度策略的同时也设置了进程优先级,输入参数 pschedparam 结构体中含有存储优先级的成员变量。

    #include <sched.h>
    int sched_setscheduler(pid_t pid, int iPolicy, const struct sched_param *pschedparam);

    也可以通过调用 sched_setparam 函数来设置进程的优先级,输入参数 pschedparam 结构体中含有存储优先级的成员变量。

    #include <sched.h>
    int sched_setparam(pid_t pid, const struct sched_param *pschedparam);

    设置满足条件的所有线程的 SylixOS 调度优先级。  

    #include <sys/resource.h>
    int setpriority(int which, id_t who, int value);
    函数 setpriority 原型分析:
    • 此函数成功时返回 0,失败时返回-1 并设置错误号;
    • 参数 which 指定参数 who 的意义;
    • 参数 who 的意义由参数 which 指定,如下图所示:
    • 参数 value 是要设置的优先级值。

    nice 函数可以调整当前进程优先级。

    #include <unistd.h>
    int nice(int incr);

    函数 nice 原型分析:

    • 此函数成功返回 0,失败返回-1 并设置错误号;
    • 参数 incr 是要调整的数值。本函数对 incr 参数的处理流程如下:

    (1) 首先获取当前进程中所有线程中的最低优先级,即数值最大的的优先级;

    (2) 然后将获取到的值和 incr 参数求和;

    (3) 将上一步求和的结果设置到当前进程的所有线程中。

     

     

     

     

    展开全文
  • POSIX线程

    2021-11-21 11:10:40
    这就是需要线程发挥作用的时候了。 什么是线程 **在一个程序中的多个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的一个控制序列。**虽然Linux和许多其他的操作系统一样,都擅长同时

    在前面,我们介绍了如何在Linux (包括UNIX)中处理讲程。类UNIX操作系统早就具备这种多进程功能了。但有时人们认为,用fork调用来创建新进程的代价太高。在这种情况下,如果能让一个进程同时做两件事情或至少看起来是这样将会非常有用。而且,你可能希望能有两件或更多的事情以一种非常紧密的方式同时发生。这就是需要线程发挥作用的时候了。

    什么是线程

    在一个程序中的多个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的一个控制序列。 虽然Linux和许多其他的操作系统一样,都擅长同时运行多个进程,但迄今为止我们看到的所有程序在执行时都是作为一个单独的进程。事实上,所有的进程都至少有一个执行线程。

    弄清楚fork系统调用和创建新线程之间的区别非常重要。当进程执行fork调用时,将创建出该进程的一份新副本。这个新进程拥有自己的变量和自己的PID,它的时间调度也是独立的,它的执行(通常)几乎完全独立于父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。

    线程的概念已经出现一段时间了,但在IEEE POSIX委员会发布有关标准之前,它们并没有在类UNIX操作系统中得到广泛支持,而且已存在的线程实现版本也因厂商的不同而有所差异。POSIX1003.1c规范的发布改变了这一切,线程不仅被很好地标准化了,而且现在绝大多数Linux发行版都已支持它。现在,多核处理器即便对于台式机也已非常普遍,大多数机器在底层硬件上就已物理支持了同时执行多个线程。而此前,对于单核CPU来说,线程的同时执行只是一个聪明、但非常有效的幻觉。

    Linux系统在1996年第一次获得线程的支持,我们常把当时使用的函数库称为LinuxThread。LinuxThread已经和POSIX的标准非常接近了(事实上,从许多方面来看,它们之前的区别并不明显),它是在Linux程序设计中迈出的很重要的一步,它使Linux程序员第一次可以在Linux系统中使用线程。但是,在Linux的线程实现版本和POSIX标准之间还是存在着细微的差别,最明显的是关于信号处理部分。这些差别中的大部分都受底层Linux内核的限制,而不是函数库实现所强加的。

    许多项目都在研究如何才能改善Linux对线程的支持,这种改善不仅仅是清除POSIX标准和Linux具体实现之间的细微的差别,而且要增强Linux线程的性能和删除一些不需要的限制,其中大部分工作都集中在如何将用户级的线程映射到内核级的线程

    本篇中的大部分代码适用于任何一种线程库,因为这些代码是基于POSIX标准的,而该标准普遍适用于所有的线程库。但是,如果使用的是旧的Linux发行版,你将看到一些细微的区别,特别是用ps命令查看示例程序的运行情况时。

    线程的优点和缺点

    在某些环境下,创建新线程要比创建新进程有更明显的优势。新线程的创建代价要比新进程小得多(虽然与其他一些操作系统相比,Linux在创建新进程方面的效率是很高的)。
    下面是一些使用线程的优点。
    有时,让程序看起来好像是在同时做两件事情是很有用的。一个经典的例子是,在编辑文档的同时对文档中的单词个数进行实时统计。一个线程负责处理用户的输入并执行文本编辑工作,另一个(它也可以看到相同的文档内容)则不断刷新单词计数变量。第一个线程(甚至可以是第三个线程)通过这个共享的计数变量让用户随时了解自己的工作进展情况。另一个例子是一个多线程的数据库服务器,这是一种明显的单进程服务多用户的情况。它会在响应一些请求的同时阻塞另外一些请求,使之等待磁盘操作,从而改善整体上的数据吞吐量。对于数据库服务器来说,这个明显的多任务工作如果用多进程的方式来完成将很难傲到高效,因为各个不同的进程必须紧密合作才能满足加锁和数据一致性方面的要求,而用多线程来完成就比用多进程要容易得多。

    一个混杂着输入、计算和输出的应用程序,可以将这几个部分分离为3个线程来执行,从而改善程序执行的性能。当输入或输出线程等待连接时,另外一个线程可以继续执行。因此,如果一个进程在任一时刻最多只能做一件事情的话,线程可以让它在等待连接之类的事情的同时做一些其他有用的事情。一个需要同时处理多个网络连接的服务器应用程序也是一个天生适用于应用多线程的例子。

    一般而言,线程之间的切换需要操作系统做的工作要比进程之间的切换少得多,因此多个线程对资源的需求要远小于多个进程。如果一个程序在逻辑上需要有多个执行线程,那么在单处理器系统上把它运行为一个多线程程序才更符合实际情况。虽然如此,编写一个多线程程序的设计困难较大,不应等闲视之。

    线程也有下面一些缺点。
    编写多线程程序需要非常仔细的设计。在多线程程序中,因时序上的细微偏差或无意造成的变量共享而引发错误的可能性是很大的。Alan Cox (Linux方面的权威,他撰写了本书的序)曾经评论线程为“如何立刻让自己自讨苦吃。”

    对多线程程序的调试要比对单线程程序的调试困难得多,因为线程之间的交互非常难于控制。将大量计算分成两个部分,并把这两个部分作为两个不同的线程来运行的程序在一台单处理器

    机器上并不一定运行得更快,除非计算确实允许它的不同部分可以被同时计算,而且运行它的机器拥有多个处理器核来支持真正的多处理。

    第一个线程程序

    线程有一套完整的与其有关的函数库调用,它们中的绝大多数函数名都以pthread_开头。为了使用这些函数库调用,我们必须定义宏_REENTRANT,在程序中包含头文件pthread.h,并且在编译程序时需要用选项-lpthread来链接线程库。

    在设计最初的UNIX和POSIX库例程时,人们假设每个进程中只有一个执行线程。一个明显的例子就是errno,该变量用于获取某个函数调用失败后的错误信息。在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数调用所改变。类似的问题还存在于fputs之类的函数中,这些函数通常用一个全局性区域来缓存输出数据。

    为解决这个问题,我们需要使用被称为可重入的例程。可重入代码可以被多次调用而仍然正常工作,这些调用可以来自不同的线程,也可以是某种形式的嵌套调用。因此,代码中的可重入部分通常只使用局部变量,这使得每次对该代码的调用都将获得它自己的唯一的一份数据副本。

    编写多线程程序时,我们通过定义宏_REENTRANT来告诉编译器我们需要可重入功能,这个宏的定义必须位于程序中的任何#include语句之前。它将为我们做3件事情,并且做得非常优雅,以至于我们一般不需要知道它到底做了哪些事。

    • 它会对部分函数重新定义它们的可安全重入的版本,这些函数的名字一般不会发生改变,只是会在函数名后面添加_r字符串。例如,函数名gethostbyname将变为gethostbyname_r。

    • stdio.h中原来以宏的形式实现的一些函数将变成可安全重入的函数。

    • 在errno.h中定义的变量errno现在将成为一个函数调用,它能够以一种多线程安全的方式来获取真正的errno值。

    在程序中包含头文件pthread.h还将向我们提供一些其他的将在代码中使用到的定义和函数原型,就如同头文件stdio.h为标准输入和标准输出例程所提供的定义一样。最后,需要确保在程序中包含了正确的线程头文件,并且在编译程序时链接了实现pthread函数的正确的线程库。有关编译线程程序的更详细的情况将在下面的实验部分中再介绍。现在,我们首先来看一个用于管理线程的新函数pthread_create,它的作用是创建一个新线程,类似于创建新进程的fork函数。它的定义如下所示:

    #include <pthread.h>
    int pthread_create(pthread_t *thread, pthread_attr_t *attr,void*(*start_routine) (voia *) , void *arg);
    

    这个函数定义看起来很复杂,其实用起来很简单。第一个参数是指向pthread_t类型数据的指针。

    线程被创建时,这个指针指向的变量中将被写入一个标识符,我们用该标识符来引用新线程。下一个参数用于设置线程的属性。我们一般不需要特殊的属性,所以只需设置该参数为NULL。最后两个参数分别告诉线程将要启动执行的函数和传递给该函数的参数。

    void *(*start_routine)(void *)
    

    上面一行告诉我们必须要传递一个函数地址,该函数以一个指向void的指针为参数,返回的也是一个指向void的指针。因此,可以传递一个任一类型的参数并返回一个任一类型的指针。用fork调用后,父子进程将在同一位置继续执行下去,只是fork调用的返回值是不同的;但对新线程来说,我们必须明确地提供给它一个函数指针,新线程将在这个新位置开始执行。

    该函数调用成功时返回值是0,如果失败则返回错误代码。手册页对这个函数以及在本章中将要介绍的其他函数的错误条件有详细的说明。
    pthread_create和大多数pthread_系列函数一样,在失败时并未遵循UNIX函数的惯例返回-1,这种情况在UNIX函数中属于一少部分。所以除非你很有把握,在对错误代码进行检查之前一定要仔细阅读使用手册中的有关内容。

    线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。注意,绝不能用它来返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量就不再存在了,这将引起严重的程序漏洞。pthread _exit函数的定义如下所示:

    #include <pthread.h>
    void pthread_exit (void *retval);
    

    pthread_join函数在线程中的作用等价于进程中用来收集子进程信息的wait函数。这个函数的定义如下所示:

    #include <pthread.h>
    int pthread_join(pthread_t th, void **thread_return) ;
    

    第一个参数指定了将要等待的线程,线程通过pthread_create返回的标识符来指定。

    第二个参数是一个指针,它指向另一个指针,而后者指向线程的返回值。与pthread_create类似,这个函数在成功时返回0,失败时返回错误代码。

    实验一个简单的线程程序

    这个程序创建一个新线程,新线程与原先的线程共享变量,并在结束时向原先的线程返回一个结果。没有比这更简单的多线程程序了!下面是程序thread1.c的代码:

    /* thread1.c */
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    
    void *thread_function(void *arg);
    char message[] ="Hello wor1d";
                 
    int main()
    {
    	int res;
    	pthread_t a_thread;
        void *thread_result;
    	res = pthread_create(&a_thread,NULL,thread_function,(void *) message);
        if(res != 0)
        {
    		perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
    	printf("waiting for thread to finish. . .\n");
     
        res = pthread_join(a_thread, &thread_result);   
        if(res != 0)
        {
            perror("Thread join failed");
            exit(EXIT_FAILURE);
        }
    	printf("Thread joined, it returned %s\n",(char *)thread_result);
        printf("Message is now %s\n",message);
    	exit(EXIT_SUCCESS);
    }
    void *thread_function(void *arg)
    {
    	printf("thread_function is running.Argument was %s\n",(char*)arg);
        sleep(3);
    	strcpy(message, "Bye!");
    	pthread_exit("Thank you for the CPU time");
    }
    

    在这里插入图片描述

    首先,我们定义了在创建线程时需要由它调用的一个函数的原型。如下所示:void *thread_function (void *arg);
    根据pthread_create的要求,它只有一个指向void的指针作为参数,返回的也是指向void的指针。稍后,我们将介绍这个函数的实现。
    在main函数中,我们首先定义了几个变量,然后调用pthread_create开始运行新线程。如下所示:

    pthread_t a_thread;
    void * thread_result;
    res = pthread_create(&a_thread,NULL,thread_function,(void *)message);
    

    我们向pthread_create函数传递了一个pthread_t类型对象的地址,今后可以用它来引用这个新线程。我们不想改变默认的线程属性,所以设置第二个参数为NULL。最后两个参数分别为将要调用的函数和一个传递给该函数的参数。
    如果这个调用成功了,就会有两个线程在运行。原先的线程(main)继续执行pthread_create后面的代码,而新线程开始执行thread_function函数。
    原先的线程在查明新线程已经启动后,将调用pthread_join函数,如下所示:res = pthread_join(a_thread,&thread_result) ;
    我们给该函数传递两个参数,一个是正在等待其结束的线程的标识符,另一个是指向线程返回值的指针。这个函数将等到它所指定的线程终止后才返回。然后主线程将打印新线程的返回值和全局变量message的值,最后退出。

    新线程在thread_function函数中开始执行,它先打印出自己的参数,休眠一会儿,然后更新全局变量,最后退出并向主线程返回一个字符串。新线程修改了数组message,而原先的线程也可以访问该数组。如果我们调用的是fork而不是pthread_create,就不会有这样的效果。

    同时执行

    接下来,我们将编写一个程序来验证两个线程的执行是同时进行的(当然,如果是在一个单处理器系统上,线程的同时执行就需要靠CPU在线程之间的快速切换来实现)。因为还未介绍到任何可以帮助我们有效地完成这一工作的线程同步函数,在这个程序中我们是在两个线程之间使用轮询技术,所以它的效率很低。同时,我们的程序仍然要利用这一事实,即除局部变量外,所有其他变量都将在一个进程中的所有线程之间共享。

    实验两个线程同时执行
    在本节中,我们创建的程序thread2 .c是在对thread1 .c稍加修改的基础上编写出来的。我们增加了另外一个全局变量来测试哪个线程正在运行。如下所示:

    /* thread2.c */
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    
    void *thread_function(void *arg);
    char message[] ="Hello wor1d";
    int run_now = 1;          
    int main()
    {
    	int res;
    	pthread_t a_thread;
    	res = pthread_create(&a_thread,NULL,thread_function,(void *) message);
        if( res != 0)
        {
            printf("thread created was failed \n");
            exit(EXIT_FAILURE);
    	}
    	int print_count1 = 0;
    	while (print_count1++ < 20) 
        {
    		if (run_now == 1)
            {
    			printf("1");
    			run_now = 2;
            }
    		else
            {
    			sleep(1);
            }
        }
    	exit(EXIT_SUCCESS);
    }
    void *thread_function(void *arg)
    {
    	int print_count2 = 0;
    	while(print_count2++ < 20) 
        {
    		if(run_now ==2)
            {
    			printf ("2");
                run_now = 1;
            }
    		else
            {
    			sleep(1);
            }
    }
    
    

    在这里插入图片描述

    每个线程通过设置run_now变量的方法来通知另一个线程开始运行,然后,它会等待另一个线程改变了这个变量的值后再次运行。这个例子显示了两个线程之间自动交替执行,同时也再次阐明了一个观点,即这两个线程共享run_now变量。

    同步

    在上一节中,我们看到两个线程同时执行的情况,但我们采用的在它们之间进行切换的方法是非常笨拙且没有效率的。幸运的是,专门有一组设计好的函数为我们提供了更好的控制线程执行和访问代码临界区域的方法。

    我们将在本节学习两种基本的方法。一种是信号量,它的作用如同看守一段代码的看门人:另一种是互斥量,它的作用如同保护代码段的一个互斥设备。这两种方法很相似,事实上,它们可以互相通过对方来实现。但在实际应用中,对于一些情况,可能使用信号量或互斥量中的一个更符合问题的语义,并且效果更好。例如,如果想控制任一时刻只能有一个线程可以访问一些共享内存,使用互斥量就要自然得多。但在控制对一组相同对象的访问时——比如从5条可用的电话线中分配1条给某个线程的情况,就更适合使用计数信号量。具体选择哪种方法取决于个人偏好和相应的程序机制。

    用信号量进行同步

    有两组接口函数用于信号量。一组取自POSIX的实时扩展,用于线程。另一组被称为系统V信号量,常用于进程的同步。这两组接口函数虽然很相近,但并不保证它们之间可以互换,而且它们使用的函数调用也各不相同。

    信号量是一个特殊类型的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作,即使在一个多线程程序中也是如此。这意味着如果一个程序中有两个(或更多)的线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。但如果是普通变量,来自同一程序中的不同线程的冲突操作所导致的结果将是不确定的。

    在本节中,我们将介绍一种最简单的信号量——二进制信号量,它只有0和1两种取值。还有一种更通用的信号量——计数信号量,它可以有更大的取值范围。信号量一般常用来保护一段代码,使其每次只能被一个执行线程运行,要完成这个工作,就要使用二进制信号量。有时,我们希望可以允许有限数目的线程执行一段指定的代码,这就需要用到计数信号量。由于计数信号量并不常用,所以我们在这里不对它进行深入的介绍,实际上它仅仅是二进制信号量的一种逻辑扩展,两者实际调用的函数都一样。

    信号量函数的名字都以sem_开头,而不像大多数线程函数那样以pthread_开头。线程中使用的基本信号量函数有4个,它们都非常的简单。
    信号量通过sem_init函数创建,它的定义如下所示:

    #include <semaphore.h>
    int sem_init (sem_t *sem,int pshared,unsigned int value);
    

    这个函数初始化由sem指向的信号量对象,设置它的共享选项(我们马上就会介绍到它),并给它一个初始的整数值。pshared参数控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则,这个信号量就可以在多个进程之间共享。给pshared参数传递一个非零值将导致调用失败。
    接下来的两个函数控制信号量的值,它们的定义如下所示:

    #include <semaphore.h>
    int sem_wait (sem_t * sem);
    int sem_post (sem_t * sem);
    

    这两个函数都以一个指针为参数,该指针指向的对象是由sem_init调用初始化的信号量。
    sem_post函数的作用是以原子操作的方式给信号量的值加1。所谓原子操作是指,如果两个线程企图同时给一个信号量加1,它们之间不会互相干扰,而不像如果两个程序同时对同一个文件进行读取、增加、写入操作时可能会引起冲突。信号量的值总是会被正确地加2,因为有两个线程试图改变它。

    sem_wait函数以原子操作的方式将信号量的值减1,但它会等待直到信号量有个非零值才会开始减法操作。因此,如果对值为2的信号量调用sem_wait,线程将继续执行,但信号量的值会减到1。如果对值为0的信号量调用sem_wait,这个函数就会等待,直到有其他线程增加了该信号量的值使其不再是0为止。如果两个线程同时在sem_wait调用上等待同一个信号量变为非零值,那么当该信号量被第三个线程增加1时,只有其中一个等待线程将开始对信号量减1,然后继续执行,另外一个线程还将继续等待。信号量的这种“在单个函数中就能原子化地进行测试和设置”的能力使其变得非常有价值。

    还有另外一个信号量函数sem_trywait,它是sem_wait的非阻塞版本。我们不在这里对它做更多的介绍、更详细的资料可以参考它的手册页。
    最后一个信号量函数是sem_destroy。这个函数的作用是,用完信号量后对它进行清理。它的定义如下:

    #include <semaphore.h>
    int sem_destroy(sem_t * sem);
    

    与前几个函数一样,这个函数也以一个信号量指针为参数,并清理该信号量拥有的所有资源。如果企图清理的信号量正被一些线程等待,就会收到一个错误。
    与大多数Linux函数一样,这些函数在成功时都返回0。

    一个线程信号量

    这个程序thread3.c也是基于thread1.c的。因为改动的地方比较多,所以我们将其完整代码列在下面。

    /* thread3.c */
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    void *thread_function (void *arg);
    sem_t bin_sem;
    #define WORK_SIZE 1024
    char work_area[WORK_SIZE];
    
    int main()
    {
    	int res;
    	pthread_t a_thread;
        void *thread_result;
    	res=sem_init(&bin_sem,0,0);
        if (res != 0)
        {
    		perror("Semaphore initialization failed");
            exit(EXIT_FAILURE);
        }
        res = pthread_create(&a_thread,NULL,thread_function,NULL);
        if (res != 0)
        {
    		perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
    	printf("Input some text Enter \"end\" to finish\n");
        while(strncmp("end",work_area,3) != 0)
        {
    		fgets(work_area,WORK_SIZE,stdin);
            sem_post(&bin_sem);
        }
    	printf("Waiting for thread to finish.. .\n");
        res = pthread_join(a_thread,&thread_result);
        if(res != 0)
        {
    		perror("Thread join failed");
            exit(EXIT_FATLURE);
        }
    	printf("Thread joined \n");
        sem_destroy(&bin_sem);
    	exit(EXIT_SUCCESS);
    }
    void *thread_function(void *arg)
    {
    	sem_wait(&bin_sem);
    	while(strncmp("end", work_area,3) != 0)
        {
    		printf("You input %d characters\n", (int)strlen(work_area)-1);
            sem_wait(&bin_sem);
        }
    	pthread_exit(NULL);
    }
    

    在这里插入图片描述

    我们将信号量的初始值设置为0。在main函数中,启动新线程后,我们从键盘读取一些文本并把它们放到工作区work_area数组中然后调用sem_post增加信号量的值。在新线程中,我们等待信号量,然后统计来自输入的字符个数。设置信号量的同时,我们等待着键盘的s输入。当输入到达时,我们释放信号量,允许第二个线程在第一个线程再次读取键盘输入之前统计出输入字符的个数。
    这两个线程共享同一个work_area数组。为了让示例代码更加简洁并容易理解,我们还省略了一些错误检查。例如,没有检查sem_wait函数的返回值。但在产品代码中,除非有特别充足的理由才省略错误检查,否则我们总是应该检查函数的返回值。

    用互斥量进行同步

    另一种用在多线程程序中的同步访问方法是使用互斥量。它允许程序员锁住某个对象,使得每次只能有一个线程访问它。为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁它。
    用于互斥量的基本函数和用于信号量的函数非常相似,它们的定义如下所示:

    #include <pthread.h>
    int pthread_mutax_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_destroy(pthread_mutex_t * mutex);
    

    与其他函数一样,成功时返回0,失败时将返回错误代码,但这些函数并不设置errno,你必须对函数的返回代码进行检查。

    与信号量类似,这些函数的参数都是一个先前声明过的对象的指针。对互斥量来说,这个对象的类型为pthread_mutex_t。 pthread_mutex_init函数中的属性参数允许我们设置互斥量的属性,而属性控制着互斥量的行为。属性类型默认为fast,但它有一个小缺点:如果程序试图对一个已经加了锁的互斥量调用pthread_mutex_lock,程序就会被阻塞,而又因为拥有互斥量的这个线程正是现在被阻塞的线程,所以互斥量就永远也不会被解锁了,程序也就进入死锁状态。这个问题可以通过改变互斥量的属性来解决,我们可以让它检查这种情况并返回一个错误,或者让它递归的操作,给同一个线程加上多个锁,但必须注意在后面执行同等数量的解锁操作。

    设置互斥量的属性超出了本次的讨论范围,所以我们将传递NULL给属性指针,从而使用其默认行为。与改变属性相关的更详细的资料可以参考pthread_mutex_init的手册页。

    线程互斥量

    这个程序也基于原先的thread1.c,但改动的地方比较多。这次,假设需要保护对一些关键变量的访问,我们用一个互斥量来保证任一时刻只能有一个线程访问它们。为了让示例代码容易阅读,我们省略了对互斥量加锁和解锁调用的返回值应该进行的一些错误检查。在软件代码中,对返回值的检查是必不可少的。下面是新程序thread4.c的代码:

    /* thread4.c */
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    #include <semaphore.h>
    void *thread_function(void *arg);
    
    pthread_mutex_t work_mutex; /*protects both work_area and time_to_exit*/
    #define WORK_SIZE 1024
    char work_area [WORK_SIZE];
    int time_to_exit = 0;
    int main()
    {
    	int res;
    	pthread_t a_thread;
        void *thread_result;
    	res = pthread_mutex_init(&work_mutex,NULL);
        if(res != 0)
        {
           perror("Mutex initialization failed");
           exit(EXIT_FAILURE);
        }
    	res = pthread_create(&a_thread,NuLL,thread_function,NULL);
        if(res != 0)
        {
    		perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
    	pthread_mutex_lock(&work_mutex);
    	printf("Input some text. Enter 'end' to finish\n");
        while(!time_to_exit)
        {
    		fgets(work_area,WORK_SIZE,stdin);
            pthread_mutex_unlock(&work_mutex);
            while(1)
            {
                pthread_mutex_lock(&work_mutex);
                if(work_area[0] != '\0')
                {
    				pthread_mutex_unlock(&work_mutex);
                    sleep(1);
                }
    			else
                {
                    break;
                }
            }
        }
    	pthread_mutex_unlock(&work_mutex);
    	printf("\nWaiting for thread to finish. ..\n");
        res = pthread_join(a_thread,&thread_result);
        if (res != 0)
        {
    		perror("Thread join failed");
            exit(EXIT_FAILURE);
        }
    	printf("Thread joined\n");
    	pthread_mutex_destroy(&work_mutex);
        exit(EXIT_SUCCESS);
    }
    void *thread_function(void *arg)
    {
    	sleep(1);
    	pthread_mutex_lock(&work_mutex);
    	while(strncmp("end",work_area,3) != 0)
        {
    		printf("You input %d characters\n",(int)strlen(work_area)-1);
            work_area[0]='\0';
    		pthread_mutex_unlock(&work_mutex);
            sleep(1);
    		pthread_mutex_lock(&work_mutex);
            while(work_area[0] == '\0')
            {
    			pthread_mutex_unlock(&work_mutex);
                sleep(1);
    			pthread_mutex_lock(&work_mutex);
            }
        }
    	time_to_exit = 1;
    	work_area[0]='\0';
    	pthread_mutex_unlock(&work_mutex);
        pthread_exit(0);
    }
    
    

    在这里插入图片描述

    在程序的开始,我们声明了一个互斥量、工作区和一个变量time_to_exit。然后初始化互斥量,接下来启动新线程。新线程首先试图对互斥量加锁。如果它已经被锁住,这个调用将被阻塞直到它被释放为止。一旦获得访问权,我们就检查是否有申请退出程序的请求。如果有,就设置time_to_exit变量,再把工作区的第一个字符设置为\0,然后退出。
    如果不想退出,就统计字符个数,然后把work_area数组中的第一个字符设置为null。我们用将第一个字符设置为null的方法通知读取输入的线程,我们已完成了字符统计。然后解锁互斥量并等待主线程继续运行。我们将周期性地尝试给互斥量加锁,如果加锁成功,就检查是否主线程又有字符送来要外理。如果还没有,就解锁互斥量继续等待:如果有,就统计字符个数并再次进入循环。

    线程的属性

    在第一次介绍创建线程的函数时,我们并未讨论更高级的线程属性问题。现在我们已介绍完了同步线程的主题,可以回头来看这些线程自身的更高级特性了。我们可以控制的线程属性非常多,但在这里我们将只介绍那些你最可能用到的,其他属性的详细资料可以在手册页中找到。

    在前面的所有程序示例中,我们都在程序退出之前用pthread_join对线程再次进行同步,如果我们想让线程向创建它的线程返回数据就需要这样做。但有时也会有这种情况,我们既不需要第二个线程向主线程返回信息,也不想让主线程等待它的结束。

    假设我们在主线程继续为用户提供服务的同时创建了第二个线程,新线程的作用是将用户正在编辑的数据文件进行备份存储。备份工作结束后,第二个线程就可以直接终止了,它没有必要再回到主线程中。

    我们可以创建这一类型的线程,它们被称为脱离线程(detached thread)。可以通过修改线程属性或调用pthread_ detach的方法来创建它们。因为本节的目的是介绍线程的属性,所以在这里我们就使用前一种方法。
    需要用到的最重要的函数是pthread_attr_init,它的作用是初始化一个线程属性对象。

    #include <pthread.h>
    int pthread_attr_init(pthread_attr_t *attr);
    

    与前面的函数一样,它在成功时返回0,失败时返回错误代码。
    还有一个回收函数pthread_attr_destroy,它的目的是对属性对象进行清理和回收。一旦对象被回收了,除非它被重新初始化,否则就不能被再次使用。

    初始化一个线程属性对象后,我们可以调用许多其他的函数来设置不同的属性行为。我们把其中主要的一些函数列在下面(完整的列表见手册页,通常位于pthread.h条目下),但只对其中的两个(detachedstate和schedpolicy)做详细的介绍:

    #include <pthread.h>
    int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
    int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);
    int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy) ;
    int pthread_attr_getschedpolicy(const pthread_attr_t *attr.int *policy);
    int pthread_attr_setschedparam (pthread_attr_t *attr, const struct sched_param*param) ;
    int pthread_attr_getschedparam(const pthread_attr_t *attr,struct sched_param*param) ;
    int pthread_attr_setinheritsched(pthread_attr_t *attr,int inherit);
    int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inherit);
    int pthread_attr_setscope(pthread_attr_t *attr, int scope) ;
    int pthread_attr_getscope(const pthread_attr_t *attr,int *scope);
    int pthread_attr_setstacksize(pthread_attr_t *attr,int scope);
    int pthread_attr_getstacksize(const pthread_attr_t *attr,int *scope);
    
    

    如你所见,可以使用的线程属性非常多。但幸运的是,你通常不需要设置太多属性就可以让程序正常工作。

    detachedstate:这个属性允许我们无需对线程进行重新合并。与大多数_set类函数一样,它以一个属性指针和一个标志为参数来确定需要的状态。pthread_attr_setdetachstate函数可能用到的两个标志分别是PTHREAD_CREATE_JOINABLE和 PTHREAD_CREATE_DETACHED。这个属性的默认标志值是PTHREAD_CREATE_JOINABLE,所以可以允许两个线程重新合并。如果标志设置为PTHREAD_CREATE_DETACHED,就不能调用pthread_join来获得另个线程的退出状态。

    schedpolicy:这个属性控制线程的调度方式。它的取值可以是SCHED_OTHER、SCHED_RP和
    SCHED_FIFO。这个属性的默认值为SCHED_OTHER。另外两种调度方式只能用于以超级用户权限运行的进程,因为它们都具备实时调度的功能,但在行为上略有区别。SCHED_RP使用循环(round-robin)调度机制,而SCHED_FIFO使用“先进先出”策略。

    schedparam:这个属性是和schedpolicy属性结合使用的,它可以对以SCHED_OTHER策略运行的线程的调度进行控制。我们将在本篇的后面看到一个使用这个属性的例子。

    inheritsched:这个属性可取两个值:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED。它的默认取值是PTHREAD_EXPLICIT_SCHED,表示调度由属性明确地设置。如果把它设置为PTHREAD_INHERIT_SCHED,新线程将沿用其创建者所使用的参数。

    scope:这个属性控制一个线程调度的计算方式。由于目前Linux只支持它的一种取值
    PTHREAD_SCOPE_SYSTEM,所以在这里我们就不做进一步介绍了。

    stacksize:这个属性控制线程创建的栈大小,单位为字节。它属于POSIX规范中的“可选”部分,只有在定义了宏_POSIX_THREAD_ATTR_STACKSIzE的实现版本中才支持。Linux在实现线程时,默认使用的栈很大,所以这个功能对Linux来说显得有些多余。

    设置脱离状态属性

    在脱离线程示例thread5.c中,我们创建一个线程属性,将其设置为脱离状态,然后用这个属性创建一个线程。子线程结束时,它照常调用pthread_exit,但这次,原先的线程不再等待与它创建的子线程重新合并。主线程通过一个简单的thread_finished标志来检测子线程是否已经结束,并显示线程之间仍然共享着变量。

    /* thread5.c */
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    void *thread_function (void *arg);
    char message[] = "Hello wor1d";
    int thread_finished = 0;
    
    int main()
    {
    	int res;
        pthread_t a_thread;
    	pthread_attr_t thread_attr;
    	res = pthread_attr_init(&thread_attr);
        if (res != 0)
        {
    		perror("Attribute creation failed");
            exit(EXIT_FAILURE);
        }
    	res = pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        if(res != 0)
        {
    		perror("Setting detached attribute failed");
            exit(EXIT_FAILURE);
        }
    	res = pthread_create(&athread,&thread_attr,thread_function,(void *) message);
                   
    	if(res != 0)
        {
    		perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
    	pthread_attr_destroy(&thread_attr);
        while(!thread_finished)
        {
    		printf("waiting for thread to say it's finished. . .\n");
            sleep(1);
        }
    	printf("Other thread finished. bye!\n");
        exit(EXIT_SUCCESS);
    }
                   
    void *thread_function(void *arg)
    {
    	printf("thread_function is running.Argument was %s\n",(char *)arg);
        sleep(4);
    	printf("Second thread setting finished flag,and exiting now\n"); 			     
        thread_finished = 1;
    	pthread_exit(NULL);
    }
    

    在这里插入图片描述

    如你所见,设置脱离状态属性可以允许第二个线程独立地完成工作,而无需原先的线程等待它。

    调度

    我们来看另外一个可能希望修改的线程属性:调度。改变调度属性和设置脱离状态非常类似,可以用sched_get_priority_max和sched_get_priority_min这两个函数来查找可用的优先级级别。实验调度
    因为这里的程序thread6.c与前面的例子很相似。

    /* thread6.c */
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    void *thread_function (void *arg);
    char message[] = "Hello wor1d";
    int thread_finished = 0;
    
    int main()
    {
    	int res;
        pthread_t a_thread;
    	pthread_attr_t thread_attr;
        int max_priority;
        int min_priority;
    	struct sched_param scheduling_value;
    
    	res = pthread_attr_init(&thread_attr);
        if (res != 0)
        {
    		perror("Attribute creation failed");
            exit(EXIT_FAILURE);
        }
    	res = pthread_attr_setschedpolicy(&thread_attr, SCHED_OTHER);
        if(res != 0)
        {
    		perror("Setting scheduling poliey failed");
            exit(EXIT_FAILURE);
        }
    	max_priority = sched_get_priority_max(SCHED_OTHER);
        min_priority = sched_get_priority_min(SCHED_OTHER);
    	scheduling_value.sched_priority = min_priority;
    	res = pthread_attr_setschedparam(&thread_attr,&scheduling_value);
        if (res != 0)
        {
    		perror("setting scheduling priority failed");
            exit(EXIT_FAILURE);
        }
    	res = pthread_create(&a_thread,&thread_attr,thread_function,(void *) message);
                   
    	if(res != 0)
        {
    		perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
    	pthread_attr_destroy(&thread_attr);
        while(!thread_finished)
        {
    		printf("waiting for thread to say it's finished. . .\n");
            sleep(1);
        }
    	printf("Other thread finished. bye!\n");
        exit(EXIT_SUCCESS);
    }
                   
    void *thread_function(void *arg)
    {
    	printf("thread_function is running.Argument was %s\n",(char *)arg);
        sleep(4);
    	printf("Second thread setting finished flag,and exiting now\n"); 			     
        thread_finished = 1;
    	pthread_exit(NULL);
    }
    

    这与设置脱离状态属性很相似,区别只是我们设置的是调度策略。

    取消一个线程

    有时,我们想让一个线程可以要求另一个线程终止,就像给它发送一个信号一样。线程有方法可以做到这一点,与信号处理一样,线程可以在被要求终止时改变其行为。
    先来看看用于请求一个线程终止的函数:

    #include <pthread.h>
    int pthread_cancel(pthread_t thread);
    

    这个函数的定义简单易懂,提供一个线程标识符,我们就可以发送请求来取消它。但在接收到取消请求的一端,事情会稍微复杂一点,不过也不是非常复杂。线程可以用pthread_setcancelstate设置自己的取消状态。

    #include <pthread.h>
    int pthread_setcancelstate(int state, int *oldstate) ;
    

    第一个参数的取值可以是PTHREAD_CANCEL_ENABLE,这个值允许线程接收取消请求;或者是PTHREAD_CANCEL_DISABLE,它的作用是忽略取消请求。oldstate指针用于获取先前的取消状态。如果你对它没有兴趣,只需传递NULL给它。如果取消请求被接受了,线程就可以进入第二个控制层次,用pthread_setcanceltype设置取消类型。

    #include <pthread.h>
    int pthread_setcanceitype(int type, int *oldtype);
    

    type参数可以有两种取值:一个是PTHREAD_CAMNCEL_ASYNCHRONOUS,它将使得在接收到取消请求后立即采取行动;另一个是PTHREAD_CANCEL_DEFERRED,它将使得在接收到取消请求后,一直等待直到线程执行了下述函数之一后才采取行动。具体是函数pthread_join、pthread_cond_wait、pthread_ cond_timedwait、pthread_testcancel、 sem_wait或sigwait。

    我们在本节中不会对它们全部进行介绍,因为并不是所有这些函数都会被经常用到。与往常一样,更详细的资料可以在它们的手册页中找到。

    oldtype参数可以保存先前的状态,如果不想知道先前的状态,可以传递NULL给它。默认情况下,线程在启动时的取消状态为PTHREAD_CANCEL_ENABLE,取消类型是PTHREAD_ CANCEL_DEFERRED。

    实验取消一个线程

    程序thread7 .c还是基于thread1.c。这一次,主线程向它创建的线程发送一个取消请求。

    /* thread7.c */
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    void *thread_function(void *arg);
    
    int main()
    {
        int res;
    	pthread_t a_thread;
        void *thread_result;
    	res = pthread_create(&a_thread,NULL, thread_function,NULL);
        if(res != 0)
        {
    		perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
    	sleep(3);
    	printf ("Canceling thread. . .\n");
        res = pthread_cancel(a_thread);
        if (res != 0)
        {
    		perror("Thread cancelation failed");
            exit(EXIT_FAILURE);
        }
    	printf("Waiting for thread to finish. ..\n");
        res = pthread_join(a_thread, &thread_result);
        if (res !=  0)
        {
    		perror("Thread join failed" );
            exit(EXIT_FAILURE);
        }
    	exit(EXIT_SUCCESS);
    }
    
    void *thread_function (void *arg)
    {
    	int i, res;
    	res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
        if (res != 0)
        {
    		perror("Thread pthread_setcancelstate failed");
            exit(EXIT_FAILURE);
        }
    	res = pthread setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
        if (res != 0)
        {
    		perror("Thread pthread_setcanceltype failed");
            exit(EXIT_FAILURE);
        }
    	printf("thread function is running\n");
        for(i = 0;i<10;i++)
        {
    		printf("Thread is stillrunning (%d) .. .\n", i);
            sleep(1);
        }
    	pthread_exit(0);
    }
    

    在这里插入图片描述

    以通常的方法创建了新线程后,主线程休眠一会儿(好让新线程有时间开始执行),然后发送一个取消请求。在新创建的线程中,我们首先将取消状态设置为允许取消,然后将取消类型设置为延迟取消,最后,线程在循环中等待被取消。

    多线程

    至此,我们总是让程序的主执行线程仅仅创建一个线程。但我们并不想让读者认为你只能多创建一个线程。

    实验多线程

    在本节最后的例子thread8 .c中,我们将演示如何在同一个程序中创建多个线程,然后又如何以不同于其启动的顺序将它们合并到一起。

    /* thread8.c */
    #include <stdio.h>
    #include <unista.h>
    #include <stalib.h>
    #include <pthread.h>
    
    #define NUM_THREADS 6
    void *thread_function (void *arg);
    
    int main()
    {
    	int res;
    	pthread_t a_thread[NUM_THREADS];
        void *thread_result;
    	int lots_of_threads;
        
    	for(lots_of_threads = 0; lots_of_threads < NUM_THREADS;lots_of_threads++)
        {
    		res = pthread_create(&(a_thread[lots_of_threads]),NULL,thread_function, (void *) &lots_of_threads);
    	if(res != 0)
        {
    		perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
    	sleep(1);
        }
    	printf ("waiting for threads to finish. . .\n");
    	for(lots_of_threads = NUM_THREADS - 1;lots_of_threads >= 0;lots_of_threads--)
        {
            res = pthread_join(a_thread[lots_of_eads],&thread_result);
            if (res == 0)
            {
    			printf("Picked up a thread\n");
            }
    		else
            {
    			perror("pthreadjoin failed");
            }
        }
    	printf("All done\n");
        exit(EXIT_SUCCESS);
    }
    void *thread_function(void *arg)
    {
    	int my_number = *(int *)arg;
        int rand_num;
    	printf("thread function is running, Argument was %d\n",my_number);
        rand_num=1+ (int)(9.0*rand()/(RAND_MAX+1.0));
    	sleep(rand_num);
    	printf("Bye from %d\n",my_number);
        pthread_exit(NULL);
    }
    
    

    在这里插入图片描述

    这一次,我们创建了一个线程ID的数组,然后通过循环创建多个线程,创建出的线程等待一段随机的时间后退出运行,在主(原先)线程中,我们等待合并这些子线程,但并不是以创建它们的顺序来合并,如果删除sleep调用后再运行这个程序,就可能会看到一些奇怪的现象,比如一些线程以相同的参数被启动,你可能会看到类似下面的输出:
    在这里插入图片描述

    你能发现为什么会出现这样的问题吗?启动线程时,线程函数的参数是一个局部变量,这个变量在循环中被更新,引起问题的代码行是:

    for(lots_of_threads = 0; lots_of_threads < NUM_THREADS;lots_of_threads++){
    res = pthread_create (&(a_thread[lots_of_threads]),NULL,thread_function,(void* )&lots_of_threads) ;
    

    如果主线程运行得足够快,就有可能改变某些线程的参数(即lots_of_threads)。当对共享变量和多个执行路径没有做到足够重视时,程序就有可能出现这样的错误行为。我们已经警告过,编写线程程序时需要在设计上特别小心。要改正这个问题,我们可以直接传递这个参数的值,对应接受参数是也需要改变一下接收方式如下所示:

    for(lots_of_threads = 0; lots_of_threads < NUM_THREADS;lots_of_threads++){
    res = pthread_create (&(a_thread[lots_of_threads]),NULL,thread_function,(void*)lots_of_threads) ;
    //接收
    int my_number = (int)arg;
    
    展开全文
  • 这是一个关于Posix线程编程的专栏。 一、 线程创建 1.1 线程与进程 可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。 1.2 创建线程 int pthread_create(pthread_t * thread, ...

    这是一个关于Posix线程编程的专栏。
      
    一、 线程创建  

    1.1 线程与进程  
    可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
     
    1.2 创建线程  
    int pthread_create(pthread_t * thread, pthread_attr_t * attr,  void * (*start_routine)(void *), void * arg)  


    与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行start_routine(arg)函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性(见下)。pthread_create()的返回值表示线程创建是否成功。尽管arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。  

    1.3 线程创建属性  
    主要包括以下几项:  
    __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为pthread_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为pthread_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到pthread_CREATE_JOINABLE状态。  

    __schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。  

    __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。  

    __inheritsched,有两种值可供选择:pthread_EXPLICIT_SCHED和pthread_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为pthread_EXPLICIT_SCHED。  

    __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:pthread_SCOPE_SYSTEM和pthread_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了pthread_SCOPE_SYSTEM一值。  

    pthread_attr_t结构中还有一些值,但不使用pthread_create()来设置。  

    为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_get---/pthread_attr_set---函数。  

    1.4 线程创建的Linux实现  
    Linux的线程实现是在核外进行的核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork(),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由__clone()传入。  

    Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行pthread库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。  

    二、线程取消  

    2.1 线程取消的定义  
    一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。  

    线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点)。  

    线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。  

    2.3 取消点  
    根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:  
    pthread_testcancel();  
    retcode = read(fd, buffer, length);  
    pthread_testcancel();  

    2.4 程序设计方面的考虑  
    如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。  

    2.5 与线程取消相关的pthread函数  
    int pthread_cancel(pthread_t thread)  
    发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。  

    int pthread_setcancelstate(int state, int *oldstate)  
    设置本线程对Cancel信号的反应,state有两种值:pthread_CANCEL_ENABLE(缺省)和pthread_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。  

    int pthread_setcanceltype(int type, int *oldtype)  
    设置本线程取消动作的执行时机,type由两种取值:pthread_CANCEL_DEFFERED和pthread_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。  

    void pthread_testcancel(void)  
    检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。 

    posix线程编程指南(2)  

    线程的私有数据。  
    有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问,比如程序可能需要每个线程维护一个链表,而使用相同的函数操作,最简单的办法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由Posix线程库维护,称为线程私有数据(Thread-specific Data,或TSD)。  

    二. 创建和注销  
    Posix定义了两个API分别用来创建和注销TSD:  

    int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))  

    该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。  

    不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的,但各个线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:  

    static struct pthread_key_struct pthread_keys[pthread_KEYS_MAX] = { { 0, NULL } };  

    创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。  

    注销一个TSD采用如下API:  

    int pthread_key_delete(pthread_key_t key)  

    这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。  

    三. 访问  
    TSD的读写都通过专门的Posix Thread函数进行,其API定义如下:  
    int pthread_setspecific(pthread_key_t key, const void *pointer)  
    void * pthread_getspecific(pthread_key_t key)  

    写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。  

    在LinuxThreads中,使用了一个位于线程描述结构(_pthread_descr_struct)中的二维void *指针数组来存放与key关联的数据,数组大小由以下几个宏来说明: #define pthread_KEY_2NDLEVEL_SIZE 32  
    #define pthread_KEY_1STLEVEL_SIZE \  
    ((pthread_KEYS_MAX + pthread_KEY_2NDLEVEL_SIZE - 1)  
    / pthread_KEY_2NDLEVEL_SIZE)  
    其中在/usr/include/bits/local_lim.h中定义了pthread_KEYS_MAX为1024,因此一维数组大小为32。而具体存放的位置由key值经过以下计算得到:  
    idx1st = key / pthread_KEY_2NDLEVEL_SIZE  
    idx2nd = key % pthread_KEY_2NDLEVEL_SIZE  

    也就是说,数据存放与一个32×32的稀疏矩阵中。同样,访问的时候也由key值经过类似计算得到数据所在位置索引,再取出其中内容返回。  

    四. 使用范例  
    以下这个例子没有什么实际意义,只是说明如何使用,以及能够使用这一机制达到存储线程私有数据的目的。  
    #include <stdio.h>  
    #include <pthread.h>  

    pthread_key_t key;  

    void echomsg(int t)  
    {  
    printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);  
    }  

    void * child1(void *arg)  
    {  
    int tid=pthread_self();  
    printf("thread %d enter\n",tid);  
    pthread_setspecific(key,(void *)tid);  
    sleep(2);  
    printf("thread %d returns %d\n",tid,pthread_getspecific(key));  
    sleep(5);  
    }  

    void * child2(void *arg)  
    {  
    int tid=pthread_self();  
    printf("thread %d enter\n",tid);  
    pthread_setspecific(key,(void *)tid);  
    sleep(1);  
    printf("thread %d returns %d\n",tid,pthread_getspecific(key));  
    sleep(5);  
    }  

    int main(void)  
    {  
    int tid1,tid2;  

    printf("hello\n");  
    pthread_key_create(&key,echomsg);  
    pthread_create(&tid1,NULL,child1,NULL);  
    pthread_create(&tid2,NULL,child2,NULL);  
    sleep(10);  
    pthread_key_delete(key);  
    printf("main thread exit\n");  
    return 0;  
    }  

    给例程创建两个线程分别设置同一个线程私有数据为自己的线程ID,为了检验其私有性,程序错开了两个线程私有数据的写入和读出的时间,从程序运行结果可以看出,两个线程对TSD的修改互不干扰。同时,当线程退出时,清理函数会自动执行,参数为tid。 

    Posix线程编程指南(3)  
    一. 互斥锁  
    二. 条件变量  
    三. 信号灯  
    四. 异步信号  
    五. 其他同步方式  

    一. 互斥锁  
    尽管在Posix Thread中同样可以使用IPC的信号量机制来实现互斥锁mutex功能,但显然semphore的功能过于强大了,在Posix Thread中定义了另外一套专门用于线程同步的mutex函数。  

    1. 创建和销毁  
    有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏pthread_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:  
    pthread_mutex_t mutex=pthread_MUTEX_INITIALIZER;  
    在LinuxThreads实现中,pthread_mutex_t是一个结构,而pthread_MUTEX_INITIALIZER则是一个结构常量。  

    动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:  
    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)  
    其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。  

    pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:  
    int pthread_mutex_destroy(pthread_mutex_t *mutex)  
    销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。  

    2. 互斥锁属性  
    互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:  

    pthread_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。  
    pthread_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。  
    pthread_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与pthread_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。  
    pthread_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。  

    3. 锁操作  
    锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。  

    int pthread_mutex_lock(pthread_mutex_t *mutex)  
    int pthread_mutex_unlock(pthread_mutex_t *mutex)  
    int pthread_mutex_trylock(pthread_mutex_t *mutex)  

    pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。  

    4. 其他  
    POSIX线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不会因收到取消信号而离开加锁等待。值得注意的是,如果线程在加锁后解锁前被取消,锁将永远保持锁定状态,因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。  



    这个锁机制同时也不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥锁,否则容易造成死锁。  

    二. 条件变量  
    条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。  

    1. 创建和注销  
    条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用pthread_COND_INITIALIZER常量,如下:  
    pthread_cond_t cond=pthread_COND_INITIALIZER  

    动态方式调用pthread_cond_init()函数,API定义如下:  
    int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)  

    尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。  

    注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:  
    int pthread_cond_destroy(pthread_cond_t *cond)  

    2. 等待和激发  
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)  
    int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)  

    等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。 

    无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(pthread_MUTEX_TIMED_NP)或者适应锁(pthread_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。  

    激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。  

    3. 其他  
    pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重新运行,在重新锁定mutex后离开pthread_cond_wait(),然后执行取消动作。也就是说如果pthread_cond_wait()被取消,mutex是保持锁定状态的,因而需要定义退出回调函数来为其解锁。  

    以下示例集中演示了互斥锁和条件变量的结合使用,以及取消对于条件等待动作的影响。在例子中,有两个线程被启动,并等待同一个条件变量,如果不使用退出回调函数(见范例中的注释部分),则tid2将在pthread_mutex_lock()处永久等待。如果使用回调函数,则tid2的条件等待及主线程的条件激发都能正常工作。  


    #include <stdio.h>  
    #include <pthread.h>  
    #include <unistd.h>  

    pthread_mutex_t mutex;  
    pthread_cond_t cond;  

    void * child1(void *arg)  
    {  
    pthread_cleanup_push(pthread_mutex_unlock,&mutex); /* comment 1 */  
    while(1){  
    printf("thread 1 get running \n");  
    printf("thread 1 pthread_mutex_lock returns %d\n",  
    pthread_mutex_lock(&mutex));  
    pthread_cond_wait(&cond,&mutex);  
    printf("thread 1 condition applied\n");  

    pthread_mutex_unlock(&mutex);  
    sleep(5);  
    }  
    pthread_cleanup_pop(0); /* comment 2 */  
    }  

    void *child2(void *arg)  
    {  
    while(1){  
    sleep(3); /* comment 3 */  
    printf("thread 2 get running.\n");  
    printf("thread 2 pthread_mutex_lock returns %d\n",  
    pthread_mutex_lock(&mutex));  
    pthread_cond_wait(&cond,&mutex);  
    printf("thread 2 condition applied\n");  
    pthread_mutex_unlock(&mutex);  
    sleep(1);  
    }  
    }  

    int main(void)  
    {  
    int tid1,tid2;  

    printf("hello, condition variable test\n");  
    pthread_mutex_init(&mutex,NULL);  
    pthread_cond_init(&cond,NULL);  
    pthread_create(&tid1,NULL,child1,NULL);  
    pthread_create(&tid2,NULL,child2,NULL);  
    do{  
    sleep(2); /* comment 4 */  
    pthread_cancel(tid1); /* comment 5 */  
    sleep(2); /* comment 6 */  
    pthread_cond_signal(&cond);  
    }while(1);  
    sleep(100);  
    pthread_exit(0);  
    }  



    如果不做注释5的pthread_cancel()动作,即使没有那些sleep()延时操作,child1和child2都能正常工作。注释3和注释4的延迟使得child1有时间完成取消动作,从而使child2能在child1退出之后进入请求锁操作。如果没有注释1和注释2的回调函数定义,系统将挂起在child2请求锁的地方;而如果同时也不做注释3和注释4的延时,child2能在child1完成取消动作以前得到控制,从而顺利执行申请锁的操作,但却可能挂起在pthread_cond_wait()中,因为其中也有申请mutex的操作。child1函数给出的是标准的条件变量的使用方式:回调函数保护,等待条件前锁定,pthread_cond_wait()返回后解锁。  

    条件变量机制不是异步信号安全的,也就是说,在信号处理函数中调用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死锁。  

    三. 信号灯  
    信号灯与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于"等待"操作,即资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作原语也意味着更多的开销。  

    信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。  

    1. 创建和注销  
    POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但LinuxThreads的实现仅有无名灯,同时有名灯除了总是可用于多进程之间以外,在使用上与无名灯并没有很大的区别,因此下面仅就无名灯进行讨论。  

    int sem_init(sem_t *sem, int pshared, unsigned int value)  
    这是创建信号灯的API,其中value为信号灯的初值,pshared表示是否为多进程共享而不仅仅是用于一个进程。LinuxThreads没有实现多进程共享信号灯,因此所有非0值的pshared输入都将使sem_init()返回-1,且置errno为ENOSYS。初始化好的信号灯由sem变量表征,用于以下点灯、灭灯操作。  

    int sem_destroy(sem_t * sem)  
    被注销的信号灯sem要求已没有线程在等待该信号灯,否则返回-1,且置errno为EBUSY。除此之外,LinuxThreads的信号灯注销函数不做其他动作。  

    2. 点灯和灭灯  
    int sem_post(sem_t * sem)  
    点灯操作将信号灯值原子地加1,表示增加一个可访问的资源。  

    int sem_wait(sem_t * sem)  
    int sem_trywait(sem_t * sem)  
    sem_wait()为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地减1,并返回。sem_trywait()为sem_wait()的非阻塞版,如果信号灯计数大于0,则原子地减1并返回0,否则立即返回-1,errno置为EAGAIN。  

    3. 获取灯值  
    int sem_getvalue(sem_t * sem, int * sval)  

    读取sem中的灯计数,存于*sval中,并返回0。  

    4. 其他  
    sem_wait()被实现为取消点,而且在支持原子"比较且交换"指令的体系结构上,sem_post()是唯一能用于异步信号处理函数的POSIX异步信号安全的API。  

    四. 异步信号  
    由于LinuxThreads是在核外使用核内轻量级进程实现的线程,所以基于内核的异步信号操作对于线程也是有效的。但同时,由于异步信号总是实际发往某个进程,所以无法实现POSIX标准所要求的"信号到达某个进程,然后再由该进程将信号分发到所有没有阻塞该信号的线程中"原语,而是只能影响到其中一个线程。  

    POSIX异步信号同时也是一个标准C库提供的功能,主要包括信号集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信号处理函数安装(sigaction())、信号阻塞控制(sigprocmask())、被阻塞信号查询(sigpending())、信号等待(sigsuspend())等,它们与发送信号的kill()等函数配合就能实现进程间异步信号功能。LinuxThreads围绕线程封装了sigaction()何raise(),本节集中讨论LinuxThreads中扩展的异步信号函数,包括pthread_sigmask()、pthread_kill()和sigwait()三个函数。毫无疑问,所有POSIX异步信号函数对于线程都是可用的。  

    int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)  
    设置线程的信号屏蔽码,语义与sigprocmask()相同,但对不允许屏蔽的Cancel信号和不允许响应的Restart信号进行了保护。被屏蔽的信号保存在信号队列中,可由sigpending()函数取出。  

    int pthread_kill(pthread_t thread, int signo)  
    向thread号线程发送signo信号。实现中在通过thread线程号定位到对应进程号以后使用kill()系统调用完成发送。  

    int sigwait(const sigset_t *set, int *sig)  
    挂起线程,等待set中指定的信号之一到达,并将到达的信号存入*sig中。POSIX标准建议在调用sigwait()等待信号以前,进程中所有线程都应屏蔽该信号,以保证仅有sigwait()的调用者获得该信号,因此,对于需要等待同步的异步信号,总是应该在创建任何线程以前调用pthread_sigmask()屏蔽该信号的处理。而且,调用sigwait()期间,原来附接在该信号上的信号处理函数不会被调用。  

    如果在等待期间接收到Cancel信号,则立即退出等待,也就是说sigwait()被实现为取消点。  

    五. 其他同步方式  
    除了上述讨论的同步方式以外,其他很多进程间通信手段对于LinuxThreads也是可用的,比如基于文件系统的IPC(管道、Unix域Socket等)、消息队列(Sys.V或者Posix的)、System V的信号灯等。只有一点需要注意,LinuxThreads在核内是作为共享存储区、共享文件系统属性、共享信号处理、共享文件描述符的独立进程看待的。 

    Posix线程编程指南(4)  
    1. 线程终止方式  
    2. 线程终止时的清理  
    3. 线程终止的同步及其返回值  
    4. 关于pthread_exit()和return  

    1. 线程终止方式  
    两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。  

    2. 线程终止时的清理  
    最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。  

    在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:  
    void pthread_cleanup_push(void (*routine) (void *), void *arg)  
    void pthread_cleanup_pop(int execute)  

    pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。  

    pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:  
    #define pthread_cleanup_push(routine,arg) \  
    { struct _pthread_cleanup_buffer _buffer; \  
    _pthread_cleanup_push (&_buffer, (routine), (arg));  
    #define pthread_cleanup_pop(execute) \  
    _pthread_cleanup_pop (&_buffer, (execute)); }  

    可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。  
    pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);  
    pthread_mutex_lock(&mut);  
    /* do some work */  
    pthread_mutex_unlock(&mut);  
    pthread_cleanup_pop(0);  

    必须要注意的是,如果线程处于pthread_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成pthread_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:  

    { int oldtype;  
    pthread_setcanceltype(pthread_CANCEL_DEFERRED, &oldtype);  
    pthread_cleanup_push(routine, arg);  
    ...  
    pthread_cleanup_pop(execute);  
    pthread_setcanceltype(oldtype, NULL);  
    }  

    3. 线程终止的同步及其返回值  
    正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。  
    void pthread_exit(void *retval)  
    int pthread_join(pthread_t th, void **thread_return)  
    int pthread_detach(pthread_t th)  

    pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。  

    如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误。  

    一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收。  

    4. 关于pthread_exit()和return  
    理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。  

    在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。  

    其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。 

    Posix线程编程指南(5) 
    1.获得本线程ID  
    2.判断两个线程是否为同一线程  
    3.仅执行一次的操作  
    4.pthread_kill_other_threads_np()  
    讲述pthread_self()、pthread_equal()和pthread_once()等杂项函数。  
    一个LinuxThreads非可移植性扩展函数pthread_kill_other_threads_np()。
    1. 获得本线程ID                 typedef unsigned long int pthread_t;
    pthread_t pthread_self(void)  
    在LinuxThreads中,每个线程都用一个pthread_descr结构来描述,其中包含了线程状态、线程ID等所有需要的数据结构,此函数的实现就是在线程栈帧中找到本线程的pthread_descr结构,然后返回其中的p_tid项。  

    2. 判断两个线程是否为同一线程  
    int pthread_equal(pthread_t thread1, pthread_t thread2)  

    在LinuxThreads中,线程ID相同的线程必然是同一个线程,因此,这个函数的实现仅仅判断thread1和thread2是否相等。  

    3. 仅执行一次的操作  
    int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))  

    本函数使用初值为pthread_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。  
    #include <stdio.h>  
    #include <pthread.h>  

    pthread_once_t once=pthread_ONCE_INIT;  

    void once_run(void)  
    {  
    printf("once_run in thread %d\n",pthread_self());  
    }  

    void * child1(void *arg)  
    {  
    int tid=pthread_self();  
    printf("thread %d enter\n",tid);  
    pthread_once(&once,once_run);  
    printf("thread %d returns\n",tid);  
    }  

    void * child2(void *arg)  
    {  
    int tid=pthread_self();  
    printf("thread %d enter\n",tid);  
    pthread_once(&once,once_run);  
    printf("thread %d returns\n",tid);  
    }  

    int main(void)  
    {  
    int tid1,tid2;  

    printf("hello\n");  
    pthread_create(&tid1,NULL,child1,NULL);  
    pthread_create(&tid2,NULL,child2,NULL);  
    sleep(10);  
    printf("main thread exit\n");  
    return 0;  
    }  

    once_run()函数仅执行一次,且究竟在哪个线程中执行是不定的,尽管pthread_once(&once,once_run)出现在两个线程中。  

    LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。如果once_control的初值不是pthread_ONCE_INIT(LinuxThreads定义为0),pthread_once()的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER(0)、IN_PROGRESS(1)、DONE(2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0。  

    4. pthread_kill_other_threads_np()  
    void pthread_kill_other_threads_np(void)  

    这个函数是LinuxThreads针对本身无法实现的POSIX约定而做的扩展。POSIX要求当进程的某一个线程执行exec*系统调用在进程空间中加载另一个程序时,当前进程的所有线程都应终止。由于LinuxThreads的局限性,该机制无法在exec中实现,因此要求线程执行exec前手工终止其他所有线程。pthread_kill_other_threads_np()的作用就是这个。  

    需要注意的是,pthread_kill_other_threads_np()并没有通过pthread_cancel()来终止线程,而是直接向管理线程发"进程退出"信号,使所有其他线程都结束运行,而不经过Cancel动作,当然也不会执行退出回调函数。尽管LinuxThreads的实验结果与文档说明相同,但代码实现中却是用的__pthread_sig_cancel信号来kill线程,应该效果与执行pthread_cancel()是一样的,其中原因目前还不清楚。

    展开全文
  • (c)返回值:成功返回0,失败返回非零错误号 二、信号量(同步) 线程信号量的使用步骤: 1、定义信号量集合 信号量集合需要我们自己定义, 比如:sem_t sem[3], 线程信号量集合其实就是一个数组,数组每个元素...

    一、互斥锁(互斥)
    使用步骤:
    1、定义一个互斥锁变量:pthread_mutex_t mutex;
    2、初始化互斥锁:预设互斥锁的初始值:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER(编译时初始化锁位解锁状态)

    初始化互斥锁的函数
    (a)函数原型
    #include <pthread.h>

                    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                                                              const pthread_mutexattr_t *restrict  attr);
                    
                    · 功能:初始化定义的互斥锁
                         什么是初始化,就是设置互斥锁所需要的值。
                                
                    · 返回值
                         总是返回0,所以这个函数不需要进行出错处理。
                         
                         
                    · 参数
                      - mutex:互斥锁,需要我们自己定义。
                         比如:pthread_mutex_t mutex;
                           
                         pthread_mutex_t是一个结构体类型,所以mutex实际上是一个结构体变量。
                         
                      - attr:互斥锁的属性
                           设置NULL表示使用默认属性,除非我们想要实现一些互斥锁的特殊功能,否则默认属
                         性就够用了。
    

    3、加锁解锁

    pthread_mutex_lock(&mutex)(阻塞加锁)访问临界区加锁操作
    pthread_mutex_trylock( &mutex)(非阻塞加锁); pthread_mutex_lock() 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。
    pthread_mutex_unlock(&mutex): 访问临界区解锁操
    4、进程退出时销毁互斥锁

    pthread_mutex_destroy
    (a)函数原型
    #include <pthread.h>

                 int pthread_mutex_destroy(pthread_mutex_t *mutex);
               
               (b)功能:销毁互斥锁
                      所谓销毁,说白了就是删除互斥锁相关的数据,释放互斥锁数据所占用的各种内存资源。
               
               (c)返回值:成功返回0,失败返回非零错误号
    

    二、信号量(同步)
    线程信号量的使用步骤:
    1、定义信号量集合

    信号量集合需要我们自己定义,

                              比如:sem_t sem[3],
                              线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。
                              
                              sem[0]:第一个信号量
                              sem[1]:第二个信号量
                              sem[2]:第三个信号量
    

    2、初始化集合中的每个信号量

    初始化信号量的函数
    (a)函数原型
    #include <semaphore.h>

                    int sem_init(sem_t *sem, int pshared, unsigned int value);
               
                    · 功能
                      初始化线程信号量集合中的某个信号量,给它设置一个初始值。
               
                    · 返回值
                      成功返回0,失败返回-1,errno被设置。
                      注意信号量的错误号不是返回的,而是设置到errno中。
                    
                    · 参数
                      - sem:信号量集合中的某个信号量
                      
                              信号量集合需要我们自己定义,
                              
                              比如:sem_t sem[3],
                              线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。
                              
                              sem[0]:第一个信号量
                              sem[1]:第二个信号量
                              sem[2]:第三个信号量
                              
                              sem_init(&sem[0], int pshared, unsigned int value);
                      
                           线程信号量集合其实就是自定义的一个数组,而进程信号量集合则是通过semget函数创建的。
                           
                           我们只要把数组定义为全局变量,所有的线程即可共享使用,不像进程信号量,需要semid
                         才能实现共享操作。
                         
                         
                      - pshared:
                        + 0:给线程使用
                        + !0:可以给进程使用
                            不过对于进程来说,我们更多的还是使用进程信号量,因为线程信号量用到
                          进程上时,存在一些不稳定的情况。
                    
                      - value:初始化值
                       对于二值信号量来说,要么是1,要么是0。
    

    3、p、v操作

    P操作
    (a)函数原型
    #include <semaphore.h>

                    int sem_wait(sem_t *sem);//阻塞p操作
            
                    
                 · 功能:阻塞p操作集合中某个信号量,值-1
                    如果能够p操作成功最好,否则就阻塞直到p操作操作成功为止。
                    
                    
                 · 返回值:成功返回0,失败返回-1,errno被设置。
                 
                 · 参数:p操作的某个信号量。
                    比如:sem_wait(&sem[0]);
                 
                 · sem_wait的兄弟函数
                      
                      int sem_trywait(sem_t *sem):不阻塞
                         如果能够p操作就p操作,如果不能p操作就出错返回,不会阻塞。
                      
                      int sem_timedwait(sem_t *sem, const struct timespec  *abs_timeout);
                         可以设置阻塞时间,如果能够p操作就p操作,不能就阻塞,如果在设置的时间内好没有
                         p操作成功就是出错返回,不再阻塞。
                      
                      这两个函数了解即可,不需要掌握,如果你真的用到了,自己举一反三即可搞定。
                    
                      
               (b)代码演示
                    sem_wait(&sem[0]); 
                      
                      
                      
            4)v操作
               (a)函数原型
                    #include <semaphore.h>
    
                    int sem_post(sem_t *sem);
               
                    · 功能:对某个信号量进行v操作,v操作不存在阻塞问题。
                         v操作成功后,信号量的值会+1
                    
                    · 返回值:成功返回0,失败返回-1,errno被设置。
                    
               (b)代码演示
                    sem_post(&sem[0]);
    
        在实际代码开发中,sem_wait和sem_post是成对出现的。
    

    4、进程结束时,删除线程信号量结合

    删除信号量
    (a)函数原型
    #include <semaphore.h>

                      int sem_destroy(sem_t *sem);
                    
                  · 功能:删除某个信号量,把所有信号量都删除后,信号量集合就被销毁。
                  
                      这与删除进程信号量集合有所不同,对于进程信号量集合来说,只要删除一个信号量,整个集合
                    即被删除,但是对于线程信号量来说,需要一个一个的删除,当所有信号量都删除完后,集合才被
                    删除完毕。
    

    三、条件变量
    条件变量的使用步骤:
    1、定义一个条件变量(全局变量)由于条件变量需要互斥锁的配合,所以还需要定义一个线程互斥锁pthread_cond_t
    2、初始化条件变量

    函数原型
    #include <pthread.h>

                 int pthread_cond_init(pthread_cond_t *restrict cond,const  pthread_condattr_t *restrict attr);
            
               · 功能
                    初始化条件变量,与互斥锁的初始化类似。
                    pthread_cond_t cond; //定义条件变量
                    pthread_cond_init(&cond, NULL); //第二个参数为NULL,表示不设置条件变量的属性。
                    
                    也可以直接初始化:
                    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//与互斥锁的初始化的原理是一样的
                 
                 
               · 返回值:成功返回0,失败返回非零错误号
               · 参数
                 - cond:条件变量
                 - attr:用于设置条件变量的属性,设置为NULL,表示使用默认属性
    

    3、使用条件变量

    2)等待条件的函数

            (a)函数原型
                 #include <pthread.h>
                 
                 int pthread_cond_wait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex);
               
                 · 功能:
                      检测条件变量cond,如果cond没有被设置,表示条件还不满足,别人还没有对cond进行设置,此时
                    pthread_cond_wait会休眠(阻塞),直到别的线程设置cond表示条件准备好后,才会被唤醒。
                    
                 · 返回值:成功返回0,失败返回非零错误号
                 
                 · 参数
                    - cond:条件变量
                    - mutex:和条件变量配合使用的互斥锁    
                 
               
            (c)pthread_cond_wait的兄弟函数
                 int pthread_cond_timedwait(pthread_cond_t *restrict cond, \
                                 pthread_mutex_t *restrict mutex, const struct timespec  *restrict abstime);
                    
                      多了第三个参数,用于设置阻塞时间,如果条件不满足时休眠(阻塞),但是不会一直休眠,
                    当时间超时后,如果cond还没有被设置,函数不再休眠。
       
                      
                      
          3)设置条件变量的函数   
          
            (a)函数原型
                 #include <pthread.h>
                 
                 int pthread_cond_signal(pthread_cond_t *cond);
                 
                 
               · 功能
                    当线程将某个数据准备好时,就可以调用该函数去设置cond,表示条件准备好了,
                 pthread_cond_wait检测到cond被设置后就不再休眠(被唤醒),线程继续运行,使用别的线程
                 准备好的数据来做事。
                    
                    当调用pthread_cond_wait函数等待条件满足的线程只有一个时,就是用pthread_cond_signal
                 来唤醒,如果说有好多线程都调用pthread_cond_wait在等待时,使用
                    int pthread_cond_broadcast(pthread_cond_t *cond);
                    
                    它可以将所有调用pthread_cond_wait而休眠的线程都唤醒。
          
          
            (b)代码演示
                 
                 调用pthread_cond_signal去设置条件变量,相当是给pthread_cond_wait发送了一个线程间专
               用的信号,通知调用pthread_cond_wait的线程,某某条件满足了,不要再睡了,赶紧做事吧。
    

    4、删除条件变量

    删除条件变量
    (a)函数原型
    #include <pthread.h>

                 int pthread_cond_destroy(pthread_cond_t *cond);
    
    展开全文
  • 翻译: 张小川,请保留原作者转载本文旨在使您熟悉使用POSIX的多线程编程,并向您展示如何在“真实”编程中使用其功能. 它解释了库定义的各种工具,展示了如何使用它们,并提供了使用它们解决编程...
  • POSIX线程不安全函数

    2021-01-12 21:23:36
    找了一天之后,终于发现,是因为在多线程函数中用了线程不安全的strtok函数导致的。char *strtok(char *s, const char *delim);功能:分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。说明:...
  • POSIX线程库:pthreads

    2021-07-13 15:38:50
    线程属性、线程模型、线程调度竞争范围6. 线程特定数据(TSD) 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"打头的; 要使用这些函数库,要通过引入头文件<pthread.h>; 链接...
  • POSIX 线程详解

    2021-01-12 21:23:41
    POSIX 线程详解一种支持内存共享的简捷工具Daniel Robbins2000 年 7 月 01 日发布线程是有趣的了解如何正确运用线程是每一个优秀程序员必备的素质。线程类似于进程。如同进程,线程由内核按时间分片进行管理。在单...
  • Linux中-POSIX 线程详解

    2021-01-12 21:23:37
    一种支持内存共享的简捷工具 摘自https://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/线程是有趣的了解如何正确运用线程是每一个优秀程序员必备的素质。线程类似于进程。如同进程,线程由内核按时间...
  • 1. POSIX简介POSIX表示可移植操作系统接口(Portable ...这里介绍的是关于POSIX线程的内容,POSIX线程的API都在pthread.h文件中,如果已经安装了,可以通过以下命令查看所有API:man -k pthread查看某个API的用...
  • POSIX线程基本操作

    2021-12-08 14:21:03
    操作系统线程部分 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 线程基本 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 线程基本目录操作系统线程部分前言一、线程是...
  • 线程中的一个函数需要创建私有数据时,该私有数据在对函数的调用之间保持一致,数据能静态地分配在存储器中,当我们采用命名范围也许可以实现它使用在函数或是文件(静态),或是全局(EXTERN)。但是当涉及到线程时就...
  • - 通过Native POSIX线程库,Linux现在更完整地实现了对POSIX线程的支持,这使得移植多线程应用程序到Linux变得更加容易了。 - Linux对UNIX平台上使用大页面支持的应用程序也提供了大页面支持功能。 - 针对具体情况...
  • POSIX线程库pthread

    2021-04-27 13:13:29
    POSIX线程库中函数的相关解释
  • 1,创建线程:pthread.hpthread_t tid;//tread_fun是线程中执行的函数pthread_create(&tid,NULL,tread_fun,"线程名称");2,互斥锁和条件变量//互斥锁pthread_mutex_t mutex;//初始化互斥锁:pthread_mutex_init(&...
  • 线程管理 线程介绍: 线程就是进程中负责执行的部分,是进程内部的控制序列。它是轻量级的,没有自己独立的内存资源、代码段(text)、数据段(静态数据bss、全局数据段data)、堆(heap)、环境变量、命令行...POSIX线程
  • Linux并行计算多线程 #include"stdio.h" #include"stdlib.h" #include<pthread.h> #define NUM_THREADS 4 long int thread_count; void * Hello(void * rank); int main(int argc, char *argv[]) { long int ...
  • } 下面三个没用过 * guard size * stack address (See unistd.h and bits/posix_opt.h _POSIX_THREAD_ATTR_STACKADDR) * stack size (default minimum PTHREAD_STACK_SIZE set in pthread.h) 创建线程时,如果写成...
  • POSIX线程Linux下,一般多线程的实现由POSIX线程编程实现。Android系统属于Linux系统,因此NDK原生支持POSIX线程编程。Windows平台一般用Windows自带的API。POSIX的编译:在Linux平台中采用gcc编译(...
  • [NDK] -POSIX线程学习一

    2021-10-07 16:19:45
    } } extern "C" JNIEXPORT void JNICALL Java_com_zhuhongxi_nativcethreaddemo1_MainActivity_posixThreads(JNIEnv *env, jobject thiz, jint threads, jint iterations) { //为每一个worker创建一个posix线程去...
  • posix线程 pthread

    2021-09-11 15:37:23
  • 使用忙等待计算ln2值的Pthreads程序 #include"stdio.h" #include"stdlib.h" #include"pthread.h" #define NUM_THREADS 8 long int thread_count; pthread_mutex_t mutex; long int n = 10000000;...
  • [NDK] -POSIX线程学习三

    2021-10-07 17:05:38
    线程同步之互斥锁 //初始化互斥锁 int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr); //锁定互斥锁 int pthread_mutex_lock(pthread_mutex_t* mutex); //解锁互斥锁 int ...
  • 虽然线程本地存储可以避免线程访问共享数据,但是线程之间的大部分数据始终还是共享的。在涉及到对共享数据进行读写操作时,就必须使用同步机制,否则就会造成线程们哄抢共享数据的结果,这会把你的数据弄的七零八落...
  • 1.线程是一个进程内部的一个控制序列。 当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。 ...
  • 今天继续学习posix IPC相关的东东,消息队列和共享内存已经学习过,接下来学习线程相关的知识,下面开始: 【注意】:创建失败这时会返回错误码,而通常函数创建失败都会返回-1,然后错误码会保存在errno当中...
  • 写一个关于线程的消费者生产者的例子这里...thread_posix.cpp // // Created by godv on 21-5-12. // #include <pthread.h> #include <stdio.h> #include "godv_log.h" //队列的头文件 #include "queue
  • POSIX线程详解

    2021-07-20 14:49:35
    第一部分简要介绍线程的概念及其使用 第二部分主要介绍互斥锁及条件变量的使用(重点探讨pthread_cond_wait) 第三部分参考运行IBM的多线程工作代码作为应用。 一、线程简介及使用 正确的使用线程是一个优秀程序员...
  • 如果我有两个线程和一个全局变量(一个线程不断循环读取变量;另一个不断循环写入它)会发生什么事情不应该? (例如:例外,错误).如果它,是什么方法来防止这种情况.我正在阅读有关互斥锁的内容,并且它们允许对一个线程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 60,511
精华内容 24,204
关键字:

posix线程