Linux之线程

目录

1、创建线程

2、线程退出

3、线程等待

4、线程脱离

5、线程资源的回收

6、线程同步之互斥锁

6.1、初始化锁

6.2、阻塞加锁

6.3、非阻塞加锁

6.4、解锁

6.5、销毁锁(此时锁必需unlock状态,否则返回EBUSY)

7、线程条件控制实现线程同步

7.1. 创建及销毁条件变量

7.2. 等待

7.3. 触发

7.4.销毁条件变量


参考博文:Linux多线程编程初探 - 峰子_仰望阳光 - 博客园

  • 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。
  • 进程本身不是基本运行单位,而是线程的容器。线程是操作系统能够进行运算调度的最小单位。
  • 线程有自己的堆栈和局部变量,但没有单独的地址空间。

        和多进程相比,多线程是一种比较节省资源的多任务操作方式。启动一个新的进程必须分配给它独立的地址空间,每个进程都有自己的堆栈段和数据段,系统开销比较高,进行数据的传递只能通过进行间通信的方式进行。在同一个进程中,可以运行多个线程,运行于同一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享全局变量和对象,启动一个线程所消耗的资源比启动一个进程所消耗的资源要少。

函数:

Linux之线程_第1张图片

1、创建线程

pthread_create函数来创建一个新的线程,函数声明:

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

参数: 

  • 参数thread为指向线程ID的地址。
  • 参数attr用于设置线程属性,NULL表示使用默认属性。
  • 参数start_routine是线程运行函数的地址,填函数名。
  • 参数arg是线程运行函数的参数。新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。若要想向start_routine传递多个参数,可以将多个参数放在一个结构体中,然后把结构体的地址作为arg参数传入

在编译时加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。

2、线程退出

线程的终止有三种方式:

1)线程的start_routine函数代码结束。

2)被同一进程中的其它线程取消,被动结束。

pthread_cancel

        pthread_cancel函数用于线程向同一进程的其他线程发送中止请求,使线程从取消点退出。该函数成功仅代表发送中止请求成功,不代表线程已安全退出。对于接收到的CANCEL信号,如何处理由该线程决定。调用pthread_cancel函数发送取消线程请求后,默认调用pthread_join函数阻塞等待释放线程资源。

        当子线程函数全部为逻辑运算函数如while(1)时,不会经过取消点,线程不能正常退出。

int pthread_cancel(pthread_t pid);
  • 设置本线程对Cancel信号的反应

int pthread_setcancelstate(int state, int *oldstate);
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状态,如果是,则进行取消动作,否则直接返回。 此函数在线程内执行,执行的位置就是线程退出的位置,在执行此函数以前,线程内部的相关资源申请一定要释放掉,否则容易造成内存泄露。

在执行大量逻辑运算时,可以调用 pthread_testcancel()作为取消点。

pthread_kill

         向子线程发送信号,线程收到信号后处理,需要在线程内部实现signal函数。

int pthread_kill(pthread_t pid, int signal);
sighandler_t signal(int signum, sighandler_t handler);

3)线程的start_routine函数调用pthread_exit结束。

void pthread_exit(void *retval);

retval是一个无类型指针。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。

3、线程等待

int pthread_join(pthread_t thread, void **retval);
  1. 调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从例程中返回或者被取消。
  2. 调用pthread_join自动把线程置于分离状态,这样线程结束资源就释放。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
  3. retval接收线程的退出状态,如果线程被取消,由retval指定的内存单元就置为PTHREAD_CANCELED。

4、线程脱离

线程有两种属性,一种是joinable,一种是detached。当一个joinable线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join,调用前线程的资源不会释放。脱离detached线程终止时,资源会立刻释放。

  • pthread_detach函数把指定的线程转变为脱离状态:
int pthread_detach(pthread_t thread);
  • 使当前线程脱离:
pthread_detach(pthread_self());

5、线程资源的回收

创建线程时默认是非detached的(joinable)。如果线程是joinable状态,当线程主函数结束时不会释放线程所占用资源。

回收线程资源,有四种方法:

1)方法一:创建线程后,在创建线程的程序中调用pthread_join等待线程退出,这种方法pthread_join会发生阻塞。

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

2)方法二:创建线程前,调用pthread_attr_setdetachstate将线程设为detached,这样线程退出时,系统自动回收线程资源。

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);  // 设置线程的属性。
pthread_create(&pthid,&attr,pth_main,NULL);

3)方法三:创建线程后,在创建线程的程序中调用pthread_detach将新创建的线程设置为detached状态。

pthread_detach(pthid);

  4)方法四:在线程主函数中调用pthread_detach改变自己的状态。

pthread_detach(pthread_self());

ps:在top命令中,如果加上-H参数,top中的每一行显示的不是进程,而是一个线程。

top -H

demo:

/*线程的创建、退出、等待*/

#include 
#include 

//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
//void pthread_exit(void *retval);
//int pthread_join(pthread_t thread, void **retval);

void *func(void *arg)
{
        //static int ret = 20;
        static char *ret = "hello";
        printf("thread1:%ld create successfully\n",pthread_self());//返回线程id
        printf("thread1:arg=%d\n",*((int*)arg));
        pthread_exit((void*)ret);
}

int main()
{
        pthread_t thread1;//unsign long型
        int arg = 10;
        char **retval;

        pthread_create(&thread1,NULL,func,(void*)&arg);//
        printf("main:%ld\n",pthread_self());

        pthread_join(thread1,(void**)retval);
        printf("main:ret=%s\n",*pret);//回收线程退出的信息

        return 0;
}

6、线程同步之互斥锁

6.1、初始化锁

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

其中参数 mutexattr 用于指定锁的属性(见下),如果为NULL则使用缺省属性。

互斥锁的属性在创建锁的时候指定,当资源被某线程锁住的时候,其它的线程在试图加锁时表现将不同。当前有四个值可供选择:

  1. PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  2. PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。
  3. PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。
  4. PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,等待解锁后重新竞争。

6.2、阻塞加锁

int pthread_mutex_lock(pthread_mutex *mutex);

如果是锁是空闲状态,本线程将获得这个锁;如果锁已经被占据,本线程将排队等待,直到成功的获取锁。

6.3、非阻塞加锁

int pthread_mutex_trylock( pthread_mutex_t *mutex);

该函数语义与 pthread_mutex_lock() 不同的是在锁已经被占据时立即返回 EBUSY,而不是挂起等待。

6.4、解锁

int pthread_mutex_unlock(pthread_mutex *mutex);

线程把自己持有的锁释放。

6.5、销毁锁(此时锁必需unlock状态,否则返回EBUSY)

int pthread_mutex_destroy(pthread_mutex *mutex);

销毁锁之前,锁必需是空闲状态(unlock)。

demo:

/*互斥量加锁解锁*/
#include 
#include 

pthread_mutex_t mutex;//定义全局变量,初始化锁

void *func1(void *arg)
{
        pthread_mutex_lock(&mutex);//加锁
        printf("thread1:%ld create successfully\n",pthread_self());//返回线程id
        printf("thread1:arg=%d\n",*((int*)arg));//打印传参
        pthread_mutex_unlock(&mutex);//解锁
        pthread_mutex_exit(NULL);
}

void *func2(void *arg)
{
        pthread_mutex_lock(&mutex);//加锁
        printf("thread2:%ld create successfully\n",pthread_self());//返回线程id
        printf("thread2:arg=%d\n",*((int*)arg));//打印传参
        pthread_mutex_unlock(&mutex);//解锁
        pthread_mutex_exit(NULL);
}

int main()
{
        pthread_t thread1;//unsign long型
        pthread_t thread2;
        int arg = 10;
        pthread_mutex_init(&mutex,NULL);//初始化锁
        pthread_create(&thread1,NULL,func1,(void*)&arg);
        pthread_create(&thread2,NULL,func2,(void*)&arg);

        printf("main:create success\n");
        printf("main:%ld\n",pthread_self());
        pthread_join(thread1,NULL);
        pthread_join(thread2,NULL);
        pthread_mutex_destroy(&mutex);//销毁锁
        return 0;
}

死锁:两个线程连续获取两个次序相反的锁,最终两个线程都无法连续获取两个锁而停滞。

线程a获取锁1然后获取锁2,线程b获取锁2然后获取锁1,两个线程都能第一把锁但是获取不到第二把锁,最终两个线程都停滞。

Linux之线程_第2张图片

7、线程条件控制实现线程同步

7.1. 创建及销毁条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
attr参数可以设置为NULL,创建一个默认属性的条件变量。

7.2. 等待

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);

  pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。

  pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。

7.3. 触发

int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);

  这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。

  注意一定要在改变条件状态以后再给线程发信号。

7.4.销毁条件变量

int pthread_cond_destroy(pthread_cond_t cond);

 demo:

/*条件控制保证线程1输出3*/
#include 
#include 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//初始化锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//初始化锁

int g_data = 0;

void *func1(void *arg)
{
        printf("thread1:%ld create successfully\n",pthread_self());//返回线程id
        while(1){
                pthread_cond_wait(&cond,&mutex);//锁住,等待条件
                printf("t1 run.........\n");
                printf("t1:%d\n",g_data);
                g_data = 0;
                sleep(1);
        }
}

//线程1等待,线程2跑,数据加到3时触发线程1,线程1打印数值3
void *func2(void *arg)
{
        printf("thread2:%ld create successfully\n",pthread_self());//返回线程id
        while(1){
                printf("t2:%d\n",g_data);
                pthread_mutex_lock(&mutex);
                g_data++;
                if(g_data==3){
                        pthread_cond_signal(&cond);
                }
                pthread_mutex_unlock(&mutex);
                sleep(1);
        }
}

int main()
{
        pthread_t thread1;//unsign long型
        pthread_t thread2;
        int arg = 10;

        pthread_create(&thread1,NULL,func1,(void*)&arg);
        pthread_create(&thread2,NULL,func2,(void*)&arg);

        pthread_join(thread1,NULL);//等待线程退出,否则主进程结束,线程就没了
        pthread_join(thread2,NULL);
                                                                                                                                                                                                                   
        pthread_mutex_destroy(&mutex);//销毁锁
        pthread_cond_destroy(&cond);//销毁条件变量
        return 0;
}

你可能感兴趣的:(c语言,多线程,linux)