Linux 多线程编程笔记

本文详细介绍了线程的创建、退出、属性修改等基础知识,并深入探讨了如何利用互斥锁和信号量进行线程间的访问控制,确保多线程环境下资源的安全共享。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程是系统中程序执行和资源分配的基本单位。每个进程都拥有自己的数据段、代码段和堆栈段。线程是在共享内存空间中并发的多道执行路径,他们共享一个进程的资源,如文件描述符和信号处理。一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响,因此,多线程中的同步就是非常重要的问题。
    头文件:/usr/include/pthread.h
    库:/usr/lib/libpthread.a
    gcc编译时需加-lpthread
   
1.线程创建和退出

    创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create。在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出一种方法。另一种退出线程的方法是使用函数pthread_exit,这是线程的主动行为。在线程中使用pthread_exit来代替进程中的exit。
    由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join可以用于将当前线程挂起,等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
   
    函数格式

    pthread_create

    所需头文件 #include <pthread.h>
    函数原型 int pthread_create ((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg))
    函数传入值 thread:       线程标识符
               attr:         线程属性设置
               start_routine:线程函数的起始地址
               arg:          传递给start_routine 的参数
    函数返回值 成功:0
               出错:-1

    pthread_exit

    所需头文件 #include <pthread.h>
    函数原型 void pthread_exit(void *retval)
    函数传入值 Retval :pthread_exit()调用者线程的返回值,可由其他函数如pthread_join来检索获取

    pthread_join

    所需头文件 #include <pthread.h>
    函数原型 int pthread_join ((pthread_t th, void **thread_return))
    函数传入值 th:等待线程的标识符
               thread_return:用户定义的指针,用来存储被等待线程的返回值(不为NULL 时)
    函数返回值 成功:0
               出错:-1

2.修改线程属性

    pthread_create函数的第二个参数——线程的属性,将该值设为NULL,也就是采用默认属性,线程的多项属性都是可以更改的。这些属性主要包括绑定属性、分离属性、堆栈地址、堆栈大小、优先级。其中系统默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
    a.绑定属性
    Linux中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,因为CPU 时间片的调度是面向内核线程(也就是轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应。而与之相对的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。
    b.分离属性
    分离属性是用来决定一个线程以什么样的方式来终止自己。在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是没有真正的终止。只有当pthread_join()函数返回时,创建的线程才能释放自己占用的系统资源。而在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。这里要注意的一点是,如果设置一个线程的分离属性,而这个线程运行又非常快,那么它很可能在pthread_create 函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create的线程就得到了错误的线程号。
    这些属性的设置都是通过一定的函数来完成的,通常首先调用pthread_attr_init函数进行初始化,之后再调用相应的属性设置函数。设置绑定属性的函数为pthread_attr_setscope,设置线程分离属性的函数为pthread_attr_setdetachstate,设置线程优先级的相关函数为pthread_attr_getschedparam(获取线程优先级)和pthread_attr_setschedparam(设置线程优先级)。在设置完这些属性后,就可以调用pthread_create函数来创建线程了。

    pthread_attr_init
    函数原型 int pthread_attr_init(pthread_attr_t *attr)
    函数传入值 attr:线程属性
    函数返回值 成功:0
               出错:-1

    pthread_attr_setscope
    函数原型 int pthread_attr_setscope(pthread_attr_t *attr, int scope)
    函数传入值 attr:线程属性
               scope: PTHREAD_SCOPE_SYSTEM:绑定
                      PTHREAD_SCOPE_PROCESS:非绑定
    函数返回值 成功:0
               出错:-1

    pthread_attr_setdetachstate
    函数原型 int pthread_attr_setscope(pthread_attr_t *attr, int detachstate)
    函数传入值 attr:线程属性
               detachstate PTHREAD_CREATE_DETACHED:分离
               PTHREAD _CREATE_JOINABLE:非分离
    函数返回值 成功:0
               出错:-1

    pthread_attr_getschedparam
    函数原型 int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param)
    函数传入值 attr:线程属性
               param:线程优先级
    函数返回值 成功:0
               出错:-1

    pthread_attr_setschedparam
    函数原型 int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param)
    函数传入值 attr:线程属性
               param:线程优先级
    函数返回值 成功:0
               出错:-1

3.线程访问控制

    主要有互斥锁和信号量。

    a.mutex互斥锁线程控制

    mutex是一种简单的加锁的方法来控制对共享资源的存取。这个互斥锁只有两种状态,也就是上锁和解锁,可以把互斥锁看作某种意义上的全局变量。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁使得共享资源按序在各个线程中操作。
    互斥锁的操作主要包括以下几个步骤。
        互斥锁初始化:pthread_mutex_init
        互斥锁上锁:pthread_mutex_lock
        互斥锁判断上锁:pthread_mutex_trylock
        互斥锁解锁:pthread_mutex_unlock
        消除互斥锁:pthread_mutex_destroy
    其中,互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时的是否需要阻塞等待。
    快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。
    递归互斥锁能够成功地返回并且增加调用线程在互斥上加锁的次数。
    检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。

    pthread_mutex_init
    函数原型 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
    函数传入值 Mutex:互斥锁
               PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
               Mutexattr PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP :创建递归互斥锁
               PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP :创建检错互斥锁
    函数返回值 成功:0
               出错:-1

    pthread_mutex_lock
    函数原型 int pthread_mutex_lock(pthread_mutex_t *mutex,)
             int pthread_mutex_trylock(pthread_mutex_t *mutex,)
             int pthread_mutex_unlock(pthread_mutex_t *mutex,)
             int pthread_mutex_destroy(pthread_mutex_t *mutex,)
    函数传入值 Mutex:互斥锁
    函数返回值 成功:0
               出错:-1

    mutex实例
/*mutex.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int lock_var;
time_t end_time;
void pthread1(void *arg);
void pthread2(void *arg);

int main(int argc, char *argv[])
{
   pthread_t id1,id2;
   pthread_t mon_th_id;
   int ret;
  
   end_time = time(NULL)+10;
   /*互斥锁初始化*/
   pthread_mutex_init(&mutex,NULL);
   /*创建两个线程*/
   ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
   if(ret!=0)
    perror("pthread cread1");
   ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
   if(ret!=0)
    perror("pthread cread2");
   pthread_join(id1,NULL);
   pthread_join(id2,NULL);
   exit(0);
}

void pthread1(void *arg)
{
       int i;
       while(time(NULL) < end_time)
       {
   /*互斥锁上锁*/
          if(pthread_mutex_lock(&mutex)!=0)
          {
             perror("pthread_mutex_lock");
          }
          else
                  printf("pthread1:pthread1 lock the variable/n");

          for(i=0;i<2;i++)
          {
                 sleep(1);
                 lock_var++;
           }
   /*互斥锁接锁*/
          if(pthread_mutex_unlock(&mutex)!=0)
          {
                 perror("pthread_mutex_unlock");
           }
          else
                 printf("pthread1:pthread1 unlock the variable/n");
          sleep(1);
       }
}

void pthread2(void *arg)
{
   int nolock=0;
   int ret;
   while(time(NULL) < end_time)
   {
    /*测试互斥锁*/
    ret=pthread_mutex_trylock(&mutex);
    if(ret==EBUSY)
    printf("pthread2:the variable is locked by pthread1/n");
    else{
    if(ret!=0)
    {
            perror("pthread_mutex_trylock");
            exit(1);
    }
    else
             printf("pthread2:pthread2 got lock.The variable is %d/n",lock_var);
    /*互斥锁接锁*/
    if(pthread_mutex_unlock(&mutex)!=0)
    {
     perror("pthread_mutex_unlock");
    }
    else
     printf("pthread2:pthread2 unlock the variable/n");
    }
    sleep(3);
   }
}

b.信号量线程控制

信号量也就是操作系统中所用到的PV原语,它广泛用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止。
    PV原语主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥,几个进程(或线程)往往只设置一个信号量sem,它们的操作流程如图9.2 所示。
    当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行.

sem_init用于创建一个信号量,并能初始化它的值。
sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。
sem_post相当于V操作,它将信号量的值加一同时发出信号唤醒等待的进程。
sem_getvalue用于得到信号量的值。
sem_destroy用于删除信号量。

sem_init
所需头文件 #include <semaphore.h>
函数原型 int sem_init(sem_t *sem,int pshared,unsigned int value)
函数传入值 sem:信号量
     pshared:决定信号量能否在几个进程间共享。由于目前Linux 还没有实现进程间共享信号量,所以这个值只能够取0
                value :信号量初始化值
    函数返回值 成功:0
               出错:-1

    sem_wait
    函数原型   int sem_wait(sem_t *sem)
               int sem_trywait(sem_t *sem)
               int sem_post(sem_t *sem)
               int sem_getvalue(sem_t *sem)
               int sem_destroy(sem_t *sem)
    函数传入值 sem:信号量
函数返回值 成功:0
               出错:-1

    实例:
    使用一个信号量实现互斥:

/*sem_mutex.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h>

int lock_var;
time_t end_time;
sem_t sem;
void pthread1(void *arg);
void pthread2(void *arg);

int main(int argc, char *argv[])
{
   pthread_t id1,id2;
   pthread_t mon_th_id;
   int ret;
   end_time = time(NULL)+30;
   /*初始化信号量为1*/
   ret=sem_init(&sem,0,1);
   if(ret!=0)
   {
          perror("sem_init");
   }
   /*创建两个线程*/
   ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
   if(ret!=0)
          perror("pthread cread1");
   ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
   if(ret!=0)
          perror("pthread cread2");
        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
   exit(0);
}

void pthread1(void *arg)
{
   int i;
   while(time(NULL) < end_time)
   {
    /*信号量减一,P操作*/
    sem_wait(&sem);
    for(i=0;i<2;i++)
    {
     sleep(1);
     lock_var++;
     printf("lock_var=%d/n",lock_var);
    }
    printf("pthread1:lock_var=%d/n",lock_var);
    /*信号量加一,V操作*/
    sem_post(&sem);
    sleep(1);
   }
}

void pthread2(void *arg)
{
   int nolock=0;
   int ret;
   while(time(NULL) < end_time)
   {
    /*信号量减一,P操作*/
    sem_wait(&sem);
    printf("pthread2:pthread1 got lock;lock_var=%d/n",lock_var);
    /*信号量加一,V操作*/
    sem_post(&sem);
    sleep(3);
   }
}

使用两个信号量实现两个线程间的同步:

/*sem_syn.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h>

int lock_var;
time_t end_time;
sem_t sem1,sem2;
void pthread1(void *arg);
void pthread2(void *arg);

int main(int argc, char *argv[])
{
   pthread_t id1,id2;
   pthread_t mon_th_id;
   int ret;
   end_time = time(NULL)+30;
   /*初始化两个信号量,一个信号量为1,一个信号量为0*/
   ret=sem_init(&sem1,0,1);
   ret=sem_init(&sem2,0,0);
   if(ret!=0)
   {
    perror("sem_init");
   }
   /*创建两个线程*/
   ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
   if(ret!=0)
    perror("pthread cread1");
   ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
   if(ret!=0)
    perror("pthread cread2");
   pthread_join(id1,NULL);
   pthread_join(id2,NULL);
   exit(0);
}

void pthread1(void *arg)
{
   int i;
   while(time(NULL) < end_time)
   {
    /*P操作信号量2*/
    sem_wait(&sem2);   
    for(i=0;i<2;i++)
    {
     sleep(1);
     lock_var++;
     printf("lock_var=%d/n",lock_var);
    }
    printf("pthread1:lock_var=%d/n",lock_var);
    /*V操作信号量1*/
    sem_post(&sem1);
    sleep(1);
   }
}

void pthread2(void *arg)
{
   int nolock=0;
   int ret;
   while(time(NULL) < end_time)
   {
    /*P操作信号量1*/
    sem_wait(&sem1);
    printf("pthread2:pthread1 got lock;lock_var=%d/n",lock_var);
    /*V操作信号量2*/
    sem_post(&sem2);
    sleep(3);
   }
}

该程序实现了先运行线程二,再运行线程一。

转自:adane 的博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值