精华内容
下载资源
问答
  • linux线程结束

    2013-09-19 18:12:43
    Linux线程有三种结束的方法: 1. 线程函数执行完毕,线程正常结束; 2. 线程调用pthread_exit(void* rval_ptr) 3. 线程被取消(也就是其他线程调用pthread_cancel(pthread_t) 这里主要总结一下线程调用pthread_...
    Linux下线程有三种结束的方法:
        1. 线程函数执行完毕,线程正常结束;
        2. 线程调用pthread_exit(void* rval_ptr)
        3. 线程被取消(也就是其他线程调用pthread_cancel(pthread_t)
    这里主要总结一下线程调用pthread_exit和pthread_cancel结束的问题。
    一. 线程被取消:
         线程被取消,就是其他线程调用pthread_cancel(pthread_t thread)方法,thread参数就是你想要取消的线程的线程ID。调用此函数是有要求的,它要求thread标识的线程的cancel state必须是PTHREAD_CANCEL_ENABLE状态,如果之前参数thread标识的线程调用了pthread_setcancelstate(int state,int*oldstate)更改线程的cancel state为PTHREAD_CANCEL_DISABLE,调用pthread_cancel就会出错。
    还有一点需要注意,默认情况下,pthread_cancel是对某线程进行延迟取消。换句话说,被取消的线程并不会立即结束,它会一直运行,知道碰到某个取消点(取消点就是操作系统判断是否线程被取消的地方),我们也可以通过使用pthread_testcancel(void)函数添加取消点;那么如果我们想当我们想让某个线程立即结束,而不是等到某个取消点才结束,怎么办?答案是更改线程的被取消类型:pthread_setcanceltype(int type,int* oldtype).将参数type更改为PTHREAD_CANCEL_ASYNCHRONOUS即可。如果又想改为线程默认取消类型,则更改为PTHREAD_CANCEL_DEFEREED。
    二. 线程退出(pthread_exit)
        使用pthread_exit(void* rval_ptr)可以从线程函数返回,并且将rval_ptr的指向的内容返回。我们可以通过使用pthread_join(void** rval_ptr)获得返回值。调用pthread必须保证线程是joinable的
    三. 线程退出函数
        线程退出函数与进程退出函数atexit()相似,使用pthread_cleanup_push(void(*rin)(void*),void*arg)对清理函数进行压栈,使用pthread_cleanup_pop(int execute)进行出栈。
    出现如下情况之一,就会调用线程退出函数:
        1. 调用了pthread_exit;
        2. 其他线程调用了pthread_cancel;
        3. 使用非0的execute调用了pthread_cleanup_pop()
    需要注意的是,如果使用0调用了pthread_cleanup_pop,函数栈只会出栈操作,而不会对函数进行调用;
    展开全文
  • Linux 结束线程

    2011-12-21 15:02:10
    pthread 线程有两种状态,joinable(非分离)状态和detachable(分离)状态,默认为joinable。  joinable:当线程函数自己返回退出或... detachable:线程结束时会自动释放资源。 Linux man page said: When


    pthread 线程有两种状态,joinable(非分离)状态和detachable(分离)状态,默认为joinable。

      joinable:当线程函数自己返回退出或pthread_exit时都不会释放线程所用资源,包括栈,线程描述符等(有人说有8k多,未经验证)。

      detachable:线程结束时会自动释放资源。

    Linux man page said:

    When a joinable thread terminates, its memory resources (thread descriptor and stack) are not deallocated until another thread performs pthread_join on it. Therefore, pthread_join must be called  once  for each joinable thread created to avoid memory leaks.

    因此,joinable 线程执行完后不使用pthread_join的话就会造成内存泄漏。

    解决办法:

    1.// 创建线程前设置 PTHREAD_CREATE_DETACHED 属性

       
    pthread_attr_t attr;
    pthread_t thread;
    pthread_attr_init (
    & attr);
    pthread_attr_setdetachstat(
    & attr, PTHREAD_CREATE_DETACHED);
    pthread_create (
    & thread, & attr, & thread_function, NULL);
    pthread_attr_destroy (
    & attr);


    2.当线程为joinable时,使用pthread_join来获取线程返回值,并释放资源。

    3.当线程为joinable时,也可在线程中调用 pthread_detach(pthread_self());来分离自己。

    展开全文
  • 主线程调用pthread__create 创建的新线程,新线程使用while(true)死循环执行动作,当执行该动作的条件不满足时,如果结束新创建的线程
  • value_ptr:通常设置为NULL,如果不为NULL,pthread_join将复制一份线程退出值到一个内存区域,并让*value_ptr指向该内存。 返回值:执行成功返回0,否则返回错误码。 2、作用 pthread_creat创建完成子...

    1、函数原型

    int pthread_join(pthread_t pid, void **value_ptr);
    1. pid:所等待的线程ID;
    2. value_ptr:通常设置为NULL,如果不为NULL,pthread_join将复制一份线程退出值到一个内存区域,并让*value_ptr指向该内存。
    3. 返回值:执行成功返回0,否则返回错误码。

    2、作用

    pthread_creat创建完成子线程后,主线程与子线程并行执行,用pthread_join可以让主线程等待子线程结束后再继续执行。

    pthread_join用于等待子线程执行结束,即子线程函数执行完毕才会返回,会一直阻塞。

    主线程调用pthread_join后,主线程会挂起,让出CPU直到该子线程执行结束。调用pthread_join让子线程执行结束后,子线程资源会自动释放。

    3、示例

    #include <pthread.h>
    #include <stdio.h>
    
    void thread_func(void)
    {
        int i;
        for(i = 0; i < 3; i++){
            printf("thread cur cnt:%d\n",i);
        }
        printf("thread_func exit\n");
        return;
    }
    int main(int argc,char *argv[])
    {
        pthread_t thrid;
        int ret;
        
        ret = thread_creat(&thrid,NULL,(void *(*)(void *))thread_func,NULL);
        if(ret){
            printf("pthread creat error:%d\n",ret);
            return -1;
        }
        pthread_join(thrid,NULL);
        return 0;
    }

     

    展开全文
  • LinuxLinux线程技术

    万次阅读 2018-09-05 15:57:23
    Linux线程概念 线程的概念 线程是计算机科学中的一个术语,是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也称为轻量进程。它是系统独立调度和分配的基本单位。同一进程中的多个线程...

    Linux多线程概念

    线程的概念

    线程是计算机科学中的一个术语,是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也称为轻量进程。它是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该进程中的全部系统资源,例如文件描述符和信号处理等。一个进程可以有很多线程,每个线程并行执行不同的任务。

    线程与进程的区别

    • 根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位;
    • 在开销方面:每个进程都必须给它分配独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段。而运行于同一进程的线程使用相同的地址空间,共享大部分数据,启动一个线程的代价也远远小于进程的代价;
    • 在通信机制方面:对于不同的进程之间,它们具有独立的数据空间,数据进行传递只能通过通信的方式进行,这种方式不仅耗时,而且不方便。但同一进程下的线程之间共享数据空间,通信很方便且安全;
    • 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行);
    • 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源;
    • 包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

     

    Linux的线程实现

    Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。Linux下pthread是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似于fork()。

    线程创建

    int pthread_create(pthread_t * restrict tidp, 
                       const pthread_attr_t * restrict attr, 
                       void *(* start_rm)(void *), 
                       void *restrict arg );

    函数说明:tidp参数是一个指向线程标识符的指针,当线程创建成功后,用来返回创建的线程ID;attr参数用于指定线程的属性,NULL表示使用默认属性;start_rtn参数为一个函数指针,指向线程创建后要调用的函数,这个函数也称为线程函数;arg参数指向传递给线程函数的参数。

    返回值:线程创建成功则返回0,发生错误时返回错误码。

    因为pthread不是Linux系统的库,所以在进行编译时要加上-lpthread,例如:

    # gcc filename -lpthread

    在代码中获得当前线程标识符的函数为:pthread_self()。

    例子:

    #include <pthread.h>
    
    void * run(void){
            printf("pthread id = %d\n", pthread_self());
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
            ret = pthread_create(&tid, NULL, run, NULL);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
    }

    线程退出

    void pthread_exit(void * rval_ptr);

    函数说明:rval_ptr参数是线程结束时的返回值,可由其他函数如pthread_join()来获取。

    如果进程中任何一个线程调用exit()或_exit(),那么整个进程都会终止。线程的正常退出方式有线程从线程函数中返回、线程可以被另一个线程终止以及线程自己调用pthread_exit()函数。

    线程等待

    在调用pthread_create()函数后,就会运行相关的线程函数了。pthread_join()是一个线程阻塞函数,调用后,则一直等待指定的线程结束才返回函数,被等待线程的资源就会被收回。

    int pthread_join(pthread_t tid, void ** rval_ptr);

    函数说明:阻塞调用函数,直到指定的线程终止。tid参数是等待退出的线程id;rval_ptr是用户定义的指针,用来存储被等待线程结束时的返回值(该参数不为NULL时)。

    例子:

    #include <pthread.h>
    
    void * run(void){
            int i=0;
            while(i<10){
                    i++;
                    printf("pthread id = %d\n", pthread_self());
                    if(i==5)
                            pthread_exit(i);        //相当于return i;
            }
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
            ret = pthread_create(&tid, NULL, run, NULL);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
            void *val;
            pthread_join(tid, &val);            //此时的val的值就是5
    }

    可以看出,pthread_exit(5)实际上就相当于return 5,也就是说,线程函数为run()函数,线程退出就是run()函数运行完。这时候就能明白pthread_join()的真正意义了。

    线程函数运行结束是可以有返回值的,这个函数的返回值怎么返回呢?可以通过return语句进行返回,也可以通过pthread_exit()函数进行返回。函数的这个返回值怎么来接收呢?就通过pthread_join()函数来接受。

    当然也可以选择不接受该线程的返回值,只阻塞该线程:

    pthread_join(tid, NULL);

    线程清除

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

    不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,如何保证线程终止时能顺利地释放自己所占用的资源,是一个必须考虑和解决的问题。

    从pthread_cleanup_push的调用点到pthread_cleanip_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

    void pthread_cleanup_push(void (* rtn)(void *), void * arg);

    函数说明:将清除函数压入清除栈。rtn是清除函数,arg是清除函数的参数。

    void pthread_cleanup_pop(int execute);

    函数说明:将清除函数弹出清除栈。执行到pthread_cleanup_pop()时,参数execute决定是否在弹出清除函数的同时执行该函数,execute非0时,执行;execute为0时,不执行。

    int pthread_cancel(pthread_t thread);

    函数说明:取消线程,该函数在其他线程中调用,用来强行杀死指定的线程。

    例子1:

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
     
    void *clean(void *arg)
    {
    	printf("clearup: %s\n", (char*)arg);
    	return (void*)0;
    }
     
    void *thr_fn1(void *arg)
    {
    	printf("thread 1 start  \n");
    	pthread_cleanup_push((void*)clean, "thread 1 first handler");
    	pthread_cleanup_push((void*)clean, "thread 1 second handler");
    	printf("thread 1 push complete \n");
    	
    	if(arg)
    	{
    		return ((void*)1);
    	}
    	
    	pthread_cleanup_pop(0);
    	pthread_cleanup_pop(0);
    	return (void *)1;
    }
     
    void *thr_fn2(void *arg)
    {
    	printf("thread 2 start \n");
    	
    	pthread_cleanup_push((void*)clean, "thread 2 first handler");
    	pthread_cleanup_push((void*)clean, "thread 2 second handler");
    	printf("thread 2 push complete \n");
    	
    	if(arg)
    	{
    		pthread_exit((void *)2);
    	}
    	pthread_cleanup_pop(0);
    	pthread_cleanup_pop(0);
    	pthread_exit((void*)2);
    }
     
    int main(void)
    {
    	int err;
    	pthread_t tid1, tid2;
    	void *tret;
    	
    	err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    	
    	if(err != 0)
    	{
    		printf("main1:error...\n");
    		return -1;
    	}
     
    	err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    	
    	if(err != 0)
    	{
    		printf("main2:error...\n");
    		return -1;
    	}
    	
    	err = pthread_join(tid1, &tret);
    	if(err != 0)
    	{
    		printf("main3:error ... \n");
    		return -1;
    	}
    	printf("thread 1 exit code %d \n", (int)tret);
    	
    	err = pthread_join(tid2, &tret);
    	
    	if(err != 0)
    	{
    		printf("main4:error ...\n");
    		return -1;
    	}
    	printf("thread 2 exit code %d \n", (int)tret);
    	return 1;
    }
    

    程序运行结果为:

    [root@localhost gcc]# ./a.out 
    thread 2 start 
    thread 2 push complete 
    clearup: thread 2 second handler
    clearup: thread 2 first handler
    thread 1 start  
    thread 1 push complete 
    thread 1 exit code 1 
    thread 2 exit code 2 
    [root@localhost gcc]# 

    例子2:

    #include <pthread.h>
    #include <stdio.h>
    #include <unistd.h>
    
    void * run(void){
            while(1){
                    printf("pthread id = %d\n", pthread_self());
            }
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
            ret = pthread_create(&tid, NULL, run, NULL);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
            pthread_cancel(tid);
            pthread_join(tid, NULL);
    }

    也就是说,pthread_exit()用于本线程自己调用,pthread_cancel()用于本线程来终结其他线程。

    同时这里也区分一下线程返回的return和pthread_exit:

    • pthread_exit()用于线程退出,可以指定返回值,以便其他线程通过pthread_join()函数获取该线程的返回值。return,是函数返回,不一定是线程函数哦! 只有线程函数中return,线程才会退出;
    • pthread_exit()、return都可以用pthread_join()来接收返回值的,也就是说,对于pthread_join()函数来说是没有区别的;
    • pthread_cleanup_push()所指定的清理函数支持调用pthread_exit()退出线程和异常终止,不支持return;
    • pthread_exit()为直接杀死/退出当前进程,return则为退出当前函数,但是在g++编译器中,main中的return会被自动优化成exit(),所以在主函数中使用return会退出该进程所有线程的运行;
    • return会调用局部对象的析构函数,而pthread_exit()不会(线程本来就不建议用pthread_exit()这类方法自杀的,正确的方法是释放所申请的内存后return)。

     

    线程函数传递及修改线程的属性

    线程函数参数传递

    在函数pthread_create()中,arg参数会被传递到start_rnt线程函数中。其中,线程函数的形参为void *类型,该类型为任意类型的指针。所以任意一种类型都可以通过地址将数据传送给线程函数中。

    例子:

    #include <pthread.h>
    #include <stdio.h>
    #include <unist.d>
    
    void * run(void * buf){
            char *str=(char *)buf;
            printf("pthread id = %d,%s\n", pthread_self(), str);
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
            char buf[256]="abcdef";
            ret = pthread_create(&tid, NULL, run, buf);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
    }

    数组作实参时,传入的是数组的首地址,即传入多个相同类型数据的首地址;结构体作实参时,传入的是结构体的地址,即传入多个不同数据类型的结构地址。

    也就是说,如果线程函数中需要传入多个不同数据类型的参数,但是依照pthread_create()的定义,仅可以传入void *的类型的数据,参数数量为一个。这个时候就需要将不同数据类型的参数封装成一个结构体,将这个结构体的地址传入。

    例子:

    #include <pthread.h>
    #include <stdio.h>
    #include <unist.d>
    
    struct STU{
            int runn;
            int num;
            char name[32];
    };
    
    void * run(void * buf){
            struct STU *p=buf;
            printf("pthread id = %d,%d,%d,%s\n", pthread_self(), p->runn, p->num, p->name);
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
            struct STU st={10,12,"aa"};
            ret = pthread_create(&tid, NULL, run, &st);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
    }

    需要注意一下,线程函数和普通的函数一样,每调用一次,局部变量都会配分一次内存,并且各自之间互不干扰。

    线程属性

    之前线程创建函数pthread_create()函数的第二个参数都设置为了NULL,也就是说,都是采用的默认的线程属性。对于大多数的程序来说,使用默认属性就够了,但还是有必要来了解一下相关的属性。

    属性结构为pthread_attr_t,属性值不能直接设置,必须使用相关的函数进行操作,初始化函数为pthread_attr_init(),这个函数必须在pthread_create()函数调用之前调用。

    属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、默认1M的堆栈、与父进程同样级别的优先级。

    线程绑定属性

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

    设置线程绑定状态的函数为pthread_attr_setscope,函数原型为:

    int pthread_attr_setscope(pthread_attr_t * tattr, int scope);

    函数说明:tattr参数为指向属性结构的指针,scope参数为绑定类型,通常有两个取值PTHREAD_SCOPE_SYSTEM(绑定)、PTHREAD_SCOPE_PROCESS(非绑定)。

    返回值,pthread_sttr_setscope()成功完成后会返回0,其他任何返回值都表示出现了错误。

    例子:

    #include <pthread.h>
    #include <stdio.h>
    #include <unist.d>
    
    void * run(void){
            printf("pthread id = %d\n", pthread_self());
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
    
            pthread_attr_t attr;
            pthread_attr_init( &attr );
            pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
    
            ret = pthread_create(&tid, NULL, run, NULL);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
    }
    

    线程分离属性

    线程的是否可结合状态决定线程以什么样的方式来终止自己。在任何一个时间点上,线程是可结合的(或非分离的,joinable)或者是分离的(detached)。 

    • 可结合属性:创建线程时,线程的默认属性是可结合的, 如果一个可结合线程结束运行但没有被pthread_join(),则它的状态类似于进程中的Zombie(僵死),即它的存储器资源(例如栈)是不释放的,所以创建线程者应该调用pthread_join()来等待线程运结束,并得到线程的退出码,回收其资源;
    • 可分离属性:通过调用pthread_detach()函数该线程的可结合属性将被修改为可分离属性。一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。 

    设置线程是否分离的函数为pthread_attr_setdatachstate(),其原型为:

    int pthread_sttr_setdetachstate(pthread_sttr_t * tattr, int detachstate);

    函数说明:tattr参数为指向属性结构的指针,detachstate参数为分离类型,通常有两个取值PTHREAD_CREATE_DETACHED(分离)、PTHREAD_CREATE_JOINABLE(非分离、结合)。

    返回值,pthread_attr_setdatachstate()成功完成后会返回0,其他任何返回值都表示出现了错误。

    例子:

    #include <pthread.h>
    #include <stdio.h>
    #include <unist.d>
    
    void * run(void){
            printf("pthread id = %d\n", pthread_self());
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
    
            pthread_attr_t attr;
            pthread_attr_init( &attr );
            pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
    
            ret = pthread_create(&tid, NULL, run, NULL);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
    
            pthread_join(tid, NULL);
    }
    

    注意,如果使用PTHREAD_CREATE_JOINABLE创建非分离线程(默认),则假设应用程序将等待线程完成。也就是说,在费线程终止后,必须要有一个线程用pthread_join()来等待它,否则就不会释放线程的资源,这将会导致内存泄漏。无论是创建的分离线程还是非分离线程,在所有线程都退出之前,进程都不会退出。

    这与进程的wait()函数类似。

    线程优先级属性

    线程优先级存放在结构sched_param中,设置线程优先级的接口是pthread_attr_setschedparam(),它的完整定义是:

    struct sched_param {
        int sched_priority;
    }
    
    int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);

    例子:

    #include <pthread.h>
    #include <stdio.h>
    #include <unist.d>
    
    void * run(void){
            printf("pthread id = %d\n", pthread_self());
            return NULL;
    }
    
    int main()
    {
            int ret;
            pthread_t tid;
    
            pthread_attr_t attr;
            pthread_attr_init( &attr );
            struct sched_param param;
            param.sched_priority=50;
            pthread_attr_setschedparam(&attr, &param);
    
            ret = pthread_create(&tid, NULL, run, NULL);
            if(ret){
                    printf("pthread create error");
                    return 1;
            }
            pthread_join(tid, NULL);
    }
    

     

    线程的互斥

    线程间的互斥是为了避免对共享资源或临界资源的同时使用,从而避免因此而产生的不可预料的后果。临界资源一次只能被一个线程使用。线程互斥关系是由于对共享资源的竞争而产生的间接制约。

    互斥锁

    假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。互斥锁用来保证一段时间内只有一个线程在执行一段代码,实现了对一个共享资源的访问进行排队等候。互斥锁是通过互斥锁变量来对访问共享资源排队访问。

    互斥量

    互斥量是pthread_mutex_t类型的变量。互斥量有两种状态:lock(上锁)、unlock(解锁)。

    当对一个互斥量加锁后,其他任何试图访问互斥量的线程都会被堵塞,直到当前线程释放互斥锁上的锁。如果释放互斥量上的锁后,有多个堵塞线程,这些线程只能按一定的顺序得到互斥量的访问权限,完成对共享资源的访问后,要对互斥量进行解锁,否则其他线程将一直处于阻塞状态。

    操作函数

    pthread_mutex_t是锁类型,用来定义互斥锁。

    互斥锁的初始化

    int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr);

    restrict,C语言中的一种类型限定符(Type Qualifiers),用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。

    函数说明:mutex为互斥量,由pthread_mutex_init调用后填写默认值;attr属性通常默认为NULL。

    上锁

    int pthread_mutex_lock(pthread_mutex_t * mutex);

    函数说明:mutex为互斥量。

    解锁

    int pthread_mutex_unlock(pthread_mutex_t * mutex);

    函数说明:mutex为互斥量。

    判断是否上锁

    int pthread_mutex_trylock(pthread_mutex_t * mutex);

    返回值:0表示已上锁,非0表示未上锁。

    销毁互斥锁

    int pthread_mutex_destory(pthread_mutex_t * mutex);

    例子:

    #include <stdio.h>
    #include <pthread.h>
    
    char str[1024];
    pthread_mutex_t mutex;
    
    void *run1(void){
            while(1){
                    pthread_mutex_lock(&mutex);
                    sprintf(str,"run1");
                    sleep(5);
                    pthread_mutex_unlock(&mutex);
                    usleep(1);
            }
    }
    
    void *run2(void){
            while(1){
                    pthread_mutex_lock(&mutex);
                    sprintf(str,"run2");
                    sleep(5);
                    pthread_mutex_unlock(&mutex);
                    usleep(1);
            }
    }
    
    int main()
    {
            pthread_mutex_init(&mutex, NULL);
            pthread_t tid1,tid2;
    
            pthread_create(&tid1,NULL,run1,NULL);
            pthread_create(&tid2,NULL,run2,NULL);
    
            pthread_join(tid1,NULL);            //默认属性joinable下,必须join()函数释放资源
            pthread_join(tid2,NULL);
    
            pthread_mutex_destory(&mutex);
    }

    这里的互斥量的用处就是在sleep(5)之间的时间内,不会切换到另一个线程的线程函数中,因为已经用互斥量锁定了。

    自旋锁

    自旋锁是一种用于保护多线程共享资源的锁,与一般互斥锁不同之处在于:当自旋锁尝试获取锁时以忙等待的形式不断地循环检查锁是否可用。在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。

    自旋锁和互斥锁的区别

    • 从实现原理上来讲,互斥锁属于sleep-waiting类型的锁,而自旋锁属于busy-waiting类型的锁。也就是说:pthread_mutex_lock()操作,如果没有锁成功的话就会调用system_wait()的系统调用并将当前线程加入该互斥锁的等待队列里;而pthread_spin_lock()则可以理解为,在一个while(1)循环中用内嵌的汇编代码实现的锁操作(在linux内核中pthread_spin_lock()操作只需要两条CPU指令,unlock()解锁操作只用一条指令就可以完成)。
    • 对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间;
    • 对于互斥锁来说,与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。

    因此自旋锁和互斥锁适用于不同的场景。自旋锁适用于那些仅需要阻塞很短时间的场景,而互斥锁适用于那些可能会阻塞很长时间的场景。

    操作函数

    pthread_spinlock_t是锁类型,用来定义自旋锁。

    自旋锁的初始化

    int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

    自旋锁的销毁

    int pthread_spin_destroy(pthread_spinlock_t *lock);

    上锁

    int pthread_spin_lock(pthread_spinlock_t *lock);

    判断是否上锁

    int pthread_spin_trylock(pthread_spinlock_t *lock);

    解锁

    int pthread_spin_unlock(pthread_spinlock_t *lock);

    C++实现自旋锁

    C++11提供了对原子操作的支持,其中std::atomic是标准库提供的一个原子类模板。

    对于lock函数,需要CAS的原子操作,可以使用std::atomic类模板的成员函数compare_exchange_strong();

    对于unlock函数,可以使用std::atomic类模板的成员函数store来以原子操作的方式将flag置false。

    #include <atomic>
     
    class spin_lock {
    private:
            std::atomic<bool> flag = ATOMIC_VAR_INIT(false);
    public:
            spin_lock() = default;
            spin_lock(const spin_lock&) = delete;
            spin_lock& operator=(const spin_lock) = delete;
            void lock(){
                    bool expected = false;
                    while(!flag.compare_exchange_strong(expected, true));        
                    //比较flag是否等于expected,相等的话,将flag设置为true(desired)
                    //flag被改变了返回true,否则返回false
                            expected = false;    
            }   
            void unlock(){   //release spin lock
                    flag.store(false);
            }   
    };
    

    参考文章:自旋锁与互斥锁的对比、手工实现自旋锁C++11实现自旋锁

     

    线程同步

    条件变量

    条件变量就是一个变量,用于线程等待某件事情的发生,当等待事件发生时,被等待的线程和事件一起继续执行。等待的线程处于睡眠状态,直到另一个线程将它唤醒,才开始活动,条件变量用于唤醒线程。

    互斥锁一个明显的缺点就是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。

    操作函数

    pthread_cond_t是条件变量类型,用于定义条件变量。

    条件变量初始化函数

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

    函数说明:cond参数为条件变量指针,通过该函数实现条件变量赋初值;attr参数通常为NULL。

    线程同步等待函数(睡眠函数)

    int pthread_cond_wait(pthread_cond_t & restrict cond, pthread_mutex_t * restrict mutex);

    函数说明:cond参数为条件变量,mutex参数为互斥量。

    说明:哪一个线程执行pthread_cond_wait,哪一个线程就开始睡眠,在睡眠时同时先解开互斥锁,以便让其他线程可以继续执行。

    发送条件信号(唤醒函数)

    int pthread_cond_signal(pthread_cond_t * cond);

    函数说明:cond参数为条件变量。

    说明:在另一个线程中使用,当某线程符合某种条件时,用于唤醒其他线程,让其他线程同步运行。其他线程被唤醒后,马上开始加锁,如果此时锁处于锁定状态,则等待被解锁后向下执行销毁。

    条件变量销毁

    int pthread_cond_destory(pthread_cond_t * cond);

    函数说明:cond参数为条件变量。

    例子:

    #include <stdio.h>
    #include <pthread.h>
    
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    bool test_cond = false;
    
    void *run1(void){
            pthread_mutex_lock(&mutex);
            while(!test_cond){
                    pthread_cond_wait(&cond, &mutex);
            }
            pthread_mutex_unlock(&mutex);
    }
    
    void *run2(void){
            pthread_mutex_lock(&mutex);
            test_cond = false;
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
    }
    
    int main()
    {
            pthread_mutex_init(&mutex, NULL);
            pthread_cond_init(&cond,NULL);
            pthread_t tid1,tid2;
    
            pthread_create(&tid1,NULL,run1,NULL);
            pthread_create(&tid2,NULL,run2,NULL);
    
            pthread_join(tid1,NULL);            //默认属性joinable下,必须join()函数释放资源
            pthread_join(tid2,NULL);
    
            pthread_mutex_destory(&cond);
            pthread_mutex_destory(&mutex);
    }

    这里讲一下:互斥量与条件变量为什么要配合使用?

    条件变量用在某个线程需要在某种条件才会进行某些操作的情况下,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。

    条件变量这个变量其实本身不包含条件信息,条件的判断不在pthread_cond_wait函数功能中,而需要外面进行条件判断。这个条件通常是多个线程或进程的共享变量,这样就很清楚了,对于共享变量很可能产生竞争条件尤其还对共享变量加了条件限制,所以从这个角度看,必须对共享变量加上互斥锁。

    加上互斥锁,就会出现因为要不断地判断条件是否成立而不断地开锁、解锁的过程。比如,线程2的任务是输出“Hello World”,但是必须满足条件iCount等于100。由于iCount是线程共享变量,所以必须使用互斥锁:

    //线程1
    while(1){
            pthead_mutex_lock(&mutex);
            if (100 < iCount)
                    iCount++;
            pthread_mutex_unlock(&mutex);
    }
    
    //线程2
    while(1){
            pthead_mutex_lock(&mutex);
            if (100 == iCount){
                    printf("Hello World\n");
                    iCount = 0;
                    pthread_mutex_unlock(&mutex);
            }else{
                    pthread_mutex_unlock(&mutex);
            }
    }

    这是很复杂的,线程2需要不断地开锁、检查、解锁,浪费了很多的系统资源。这个时候就有一个办法了,让线程2堵塞休眠,当线程1达到条件的时候直接通知线程2,把它唤醒,这样就避免浪费了。

    同时还要注意一个过程:

    pthread_cond_wait():这个函数的过程我们必须了解,首先对互斥锁进行解锁;然后自身堵塞等待;当等待条件达成,注意这时候函数并未返回,而是重新获得锁并返回。

    所以:pthread_cond_wait()本身提供的重要功能是,保证这些操作一定是原子操作不可分割。

    试想一下,如果进程A调用了pthread_cond_wait(),先进行了解锁,这时候由于进程A时间片到期,轮换到进程B,进程B一直想要这把锁,现在终于拿到了,它干完了事情,调用pthread_cond_signal()想唤醒A但是A并未完成睡眠等待条件达成,所以这个唤醒信号就丢失了。

    参考文章:关于条件变量和互斥锁为何配合使用的思考

     

    信号量

    信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问,也被称为PV原子操作。

    PV原子操作,广泛用于进程或线程之间的通信的同步和互斥。其中,P是通过的意思,V是释放的意思,不可中断的过程,则由操作系统来保证P操作和V操作。PV操作时针对信号量的操作,就是对信号量进行加减的过程。

    • P操作,即信号量sem减一的过程,如果sem小于等于0,P操作被堵塞,直到sem变量大于0为止。P操作即加锁过程。
    • V操作,即信号量sem加一的过程。V操作即解锁过程。

    操作函数

    信号量初始化

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

    函数说明:sem参数是信号量指针;pshared参数为共享方式,0表示信号量只是在当前进程中使用(线程),1表示信号量在多进程中使用;value参数表示信号量的初始值,一般为1。

    P操作,减少信号量

    int sem_wait(sem_t *sem);

    V操作,增加信号量

    int sem_post(sem_t *sem);

    销毁信号量

    int sem_destory(sem_t *sem);

    获取信号量的值

    int sem_getvalue(sem_t *sem, int *sval);

    例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    sem_t sem;
    
    void *run1(void){
            while(1){
                    sem_wait(&sem);
                    printf("run1 id=%d",pthread_self());
                    sem_post(&sem);
                    usleep(1);
            }
    }
    
    void *run2(void){
            while(1){
                    sem_wait(&sem);
                    printf("run2 id=%d",pthread_self());
                    sem_post(&sem);
                    usleep(1);
            }
    }
    
    int main()
    {
            pthread_t tid1,tid2;
            sem_init(&sem,0,1);
    
            pthread_create(&tid1,NULL,run1,NULL);
            pthread_create(&tid2,NULL,run2,NULL);
    
            pthread_join(tid1,NULL);            //默认属性joinable下,必须join()函数释放资源
            pthread_join(tid2,NULL);
    
            sem_destory(&sem);
    }

     

    展开全文
  • 1、线程结束的方式 (1)线程函数中调用pthread_exit函数,不会导致对象析构,可以使用 (2)线程所属的进程结束,进程调用exit,线程C++对象不会销毁,不安全,属于被动结束 (3)线程函数执行返回return,好的...
  • 下面以一个例子来说明线程结束后的善后处理: 执行结果: 只有调用了pthread_exit的时候才会调用线程结束的善后处理程序!
  • linux线程之pthread_cancel结束线程

    千次阅读 2012-07-10 14:14:43
    这篇文章主要从一个 Linux 下一个 pthread_cancel 函数引起的多线程死锁小例子出发来说明 Linux 系统对 POSIX 线程取消点的实现方式,以及如何避免因此产生的线程死锁。 目录: 1. 一个 pthread_cancel 引起的线程...
  • Linux线程的几种结束方式

    千次阅读 2015-11-17 11:41:22
    Linux创建线程使用int pthread_create(pthread_t *thread, const pthread_... void *(*start_routine)(void *), void *arg)Linux线程的几种结束方式: 调用pthread_exit(exit_code),exit_code为线程退出的状态代码。同
  • Linux结束线程的方法,深度好文

    千次阅读 2016-07-06 20:28:01
    Linux结束线程的方法,深度好文。  引用 http://www.cnblogs.com/Creator/archive/2012/03/21/2408413.html 》 ndk不支持pthread_cancel, pthread_join也是调用了也无效(在被结束线程阻塞的情况下)。只支持...
  • Linux线程

    千次阅读 2016-05-05 17:26:41
    实例先看一个多线程编程的实例,一般我们的程序只有一个线程,但是在许多大型的...编写Linux下的线程需要包含头文件pthread.h,在生成可执行文件的时候需要链接库libpthread.a或者libpthread.soLinux下线程创建函数pthr
  • Linux线程

    2017-06-18 15:05:26
    Linux下没有真正意义的线程,是使用进程模拟的,在Linux下,线程叫做轻量级进程,使用task_struct结构体描述,而Windows下存在线程TCB。 3.线程与进程的区别 ①进程强调资源独占,线程强调资源共享; ②进程是...
  • <br />在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或...
  • linux线程pthread

    万次阅读 2010-10-04 11:58:00
    1.Linux线程的发展  早在LINUX2.2内核中。并不存在真正意义上的线程,当时Linux中常用的线程pthread实际上是通过进程来模拟的,也就是同过fork来创建“轻”进程,并且这种轻进程的线程也有个数的限制:...
  • 下面小编就为大家带来一篇Linux线程环境下 关于进程线程终止函数总结。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • linux线程编程

    2015-11-24 18:53:16
    1. Linux线程介绍 线程的概念早在上世纪60年代就被提出,知道上世纪80中期才被真正使用起来。Solaris是线程使用的先驱,在传统的UNIX系统中,一个线程就对应一个进程,多线程类似于多进程,线程的左右没有得到很...
  • linux内核线程

    万次阅读 2018-10-16 18:22:49
    内核经常需要在后台执行一些操作,这种任务就可以通过内核线程(kernle thread)完成,内核线程是独立运行在内核空间的标准进程。...实际上,内核线程只能由其他内核线程创建,linux驱动模块中可以用kernel_threa...
  • Linux线程编程例子—生产者与消费者的实验基础上,实现即时“ESC”按键结束所有线程并退出。
  • linux线程与线程安全

    千次阅读 2016-05-25 15:31:15
    linux线程与线程安全 0.在linux中创建一个线程 main.cpp #include #include void *thread_function(void *dummyPtr) { printf("Thread number %ld\n", pthread_self()); } int main(int argc, char *argv[]...
  • Linux线程

    千次阅读 2009-01-08 16:14:00
    一:Linux线程编程: 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一...
  • t 甚或几 p 的数据的数据库系统,到手机上的一个有良好用户响应能力的 app,为了充分利用每个 CPU 内核,都会想到是否可以使用多线程技术。这里所说的“充分利用”包含了两个层面的意思,一
  • Linux线程编程 线程属性

    千次阅读 2015-11-25 15:19:21
    1. 线程属性的结构 一般情况来说,当我们需要使用多线程时直接调用pthread_create()函数的默认参数即可满足我们的需求,但有些时候我们也需要调整线程的属性。线程的属性结构为pthread_attr_t,在中定义,原型如下...
  • 1. 线程的创建
  • Linux内核线程

    千次阅读 2013-08-14 15:15:56
    内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程,与系统中其他进程“并行”执行(实际上,也并行于内核自身的执行),内核线程经常被称为内核“守护进程”。它们主要用于执行下列...
  • linux线程学习

    千次阅读 2012-07-18 16:55:38
    1.Linux线程”  进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型。Linux是一种“多进程单线程”的操作系统。Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是...
  • Linux线程编程

    千次阅读 2019-06-21 17:40:29
    作为多任务实现的一种机制,多线程应用得非常广泛,相对于多进程,多线程不仅运行效率高,而且还可以提高系统资源的使用效率。虽然网上关于多线程的讲解已经有一大堆,但出于学习的心态,有必要在这里做一下笔记。 ...
  • Linux线程编程(不限Linux

    千次阅读 2011-11-11 16:57:18
    ——本文一个例子展开,介绍Linux下面线程的操作、多线程的同步和互斥。 前言 线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,如线程之间怎样同步、...
  • Linux线程使用详解

    万次阅读 2015-12-30 21:14:58
    Linux线程和进程的区别:http://blog.csdn.net/qq_21792169/article/details/50437304 线程退出的条件:下面任意一个都可以。 1.调用pthread_exit函数退出。 2.其他线程调用pthread_cancel取消该...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 144,057
精华内容 57,622
关键字:

linux总线程结束

linux 订阅