线程--同步--互斥--死锁

发布于:2023-01-10 ⋅ 阅读:(664) ⋅ 点赞:(0)

什么是线程?与进程之间的关系?

进程:是cpu分配资源的最小单位

线程:是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位

进程只是维护程序所需的资源,线程才是真正的执行体.所以一个进程至少包含一个线程.

多线程的好处?

  • 在只有一个控制线程下,单线程进程要完成多个任务,多线程下只需要把任务分解,为每个任务分配一个单独的线程,相互独立的任务就可以交叉进行,提高代码的吞吐量        
  • 多线程自动的访问相同的存储地址空间和文件描述符

一个进程的所有信息对该进程的所有线程都是共享的

每个线程都含有表示执行环境所必须的信息(非共享的 )

1)线程id

4) errno变量

5) 信号屏蔽字

6) 调度优先级

 线程标识

进程id在整个系统是唯一的,但线程id不同,只在他所属进程上下文中才有意义.

进程id使用pid-t数据类型表示,是一个非负整数.

线程id是用pthread_t数据类型表示,实现的时候用一个结构来代表pthread_t数据类型,所以可移植操作系统实现不能把他作为整数处理,

 用结构表示pthread_t数据类型,我们可以通过pthread_self(获取线程id)pthread_equal(比较是否相等)

  #include<stdio.h>    
  #include <pthread.h>    
  int main()    
  {    
      pthread_t tid;    
      tid=pthread_self();    
      printf("tid=%d.....\n",tid);    
      if(pthread_equal(tid,pthread_self()))    
      {    
          printf("equal............\n");    
      }    
      else{    
          printf("no equal......\n");                                                                                                                                      
      }    
      return 0;    
  }

线程的创建


#include <pthread.h>
​
int pthread_create(pthread_t *thread,
            const pthread_attr_t *attr,
            void *(*start_routine)(void *),
            void *arg );
功能:
    创建一个线程。
参数:
    thread:线程标识符地址。
    attr:线程属性结构体地址,通常设置为 NULL。
    start_routine:线程函数的入口地址。
    arg:传给线程函数的参数。
返回值:
    成功:0
    失败:非 0
  #include<stdio.h>    
  #include<pthread.h>    
  #include<unistd.h>    
void* fun(void *arg)    
  {    
    pthread_t ntid;    
    printf("fun:%d\n",pthread_self());    
      printf("pthread......run......\n");    
      return (void*)0;    
  }    
  int main()    
  {    
      pthread_t tid;    
      
    pid_t pid;    
      pthread_create(&tid,NULL,fun,0);    
      sleep(2);//主线程需要休眠,否则新线程创建不出来就退出了                                                                                                               
      tid=pthread_self();               
     printf("main:%d,%d\n",tid,getpid());    
                                        
      return 0;                         
  }

线程终止

任意线程调用exit.和_exit会使整个进程终止

单个线程可以通过3种方式退出(在进程不终止的情况下)

  • 从执行函数中退出
  • 调用pthread_exit退出线程
  • 可以被同一进程其他线程取消
#include <pthread.h>
​
void pthread_exit(void *retval);
功能:
    退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
参数:
    retval:存储线程退出状态的指针。
返回值:无  
​
#include <pthread.h>
​
int pthread_join(pthread_t thread, void **retval);
功能:
    等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:
    thread:被等待的线程号。
    retval:用来存储线程退出状态的指针的地址。
返回值:
    成功:0
    失败:非 0

 如果对返回值不感兴趣就将rval_ptr设为null,不获取线程的终止状态

如何让获取已终止程序的状态码

#include<stdio.h>    
  #include<pthread.h>    
W>void* fun1(void*arg)    
  {    
      printf("fun1....return ......\n");    
      return (void*)123;    
  }    
W>void * fun2(void *arg)    
  {    
      printf("fun2......exit...........\n");    
      pthread_exit((void*)2);    
  }    
  int main()    
  {    
      pthread_t tid1;    
      pthread_t tid2;    
  //状态码    
  void *ret;    
      
      //创建线程    
      pthread_create(&tid1,NULL,fun1,0);    
      pthread_create(&tid2,NULL,fun2,0);    
      
      //等待结束    
      pthread_join(tid1,&ret);    
      printf("thread 1 return.....%ld\n",(long)ret);    
      pthread_join(tid2,&ret);    
      printf("thread 2 exit.......%ld\n",(long)ret);    
      
      return 0;    
  }

无类型指针参数可以传递包含复杂信息的结构的地址

 变量(分配在栈上)作为pthread_exit的参数时出现问题

#include<stdio.h>    
  #include<pthread.h>    
  #include<unistd.h>    
  struct data {    
  int a,b,c,d;    
  };    
  void printfdata(char*s,struct data*fd)    
  {    
      printf("%s",s);    
      printf("struct at 0x%lx\n",(long)fd);    
      printf("fd->a:%d\n",fd->a);    
      printf("fd->b:%d\n",fd->b);    
      printf("fd->c:%d\n",fd->c);    
      printf("fd->d:%d\n",fd->d);    
      
  }    
void *fun1(void*arg)    
  {    
      struct data fd={1,2,3,4};    
    printfdata("thread 1\n",&fd);    
      pthread_exit((void*)&fd);    
  }    
void *fun2(void *arg)    
  {    
  printf("thread 2:id is %lu\n",pthread_self());    
  pthread_exit((void*)0);                                                                                                                                                  
  }    
  int main()    
  {    
          
      pthread_t tid1;    
      pthread_t tid2; 
       struct data *fd;
  //创建线程
  int ret= pthread_create(&tid1,NULL,fun1,NULL);
      if(ret!=0)
      {
          printf("thread 1 error..........\n");                                                                                                                            
          return 1;
      }
ret=pthread_join(tid1,(void *)&fd);
  if(ret!=0)
  {
      printf("join.........error\n");
      return 1;
  };
  sleep(2);
  printf("starting second thread.....\n");
  pthread_create(&tid2,NULL,fun2,NULL);
  sleep(1);
printfdata("second:\n",fd);
      return 0;
  }

 线程可以调用pthread_cancel()函数来请求取消同一进程的其他进程(只是请求并不一定取消)

线程分离(简单说一下)

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。

不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。


#include <pthread.h>
​
int pthread_detach(pthread_t thread);
功能:
    使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,
线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当
被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
参数:
    thread:线程号。
返回值:
    成功:0
    失败:非0

#include<stdio.h>    
  #include<pthread.h>    
  #include<unistd.h>    
W>void*fun(void*arg)    
  {    
      printf("cccccccccccccccc\n");    
      pthread_exit((void*)1);    
  }    
  int main()    
  {    
      pthread_t tid;    
      pthread_create(&tid,NULL,fun,NULL);    
  sleep(2);//保证线程创建成功                                                                                                                                              
     // 设置分离    
     pthread_detach(tid);    
      
      return 0;    
  

线程同步

两个或两个以上线程在访问同一种资源先后顺序完成指定任务

如果两个或两个以上线程不同步会出现数据错误

使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。

例子

#include<stdio.h>
  #include<pthread.h>
  #include<unistd.h>
void *fun1(void*arg)
  {
      for(int i='A';i<='Z';i++)
      {    
           printf("%c",i);    
           fflush(stdout);    
           usleep(100000);    
      }    
      pthread_exit((void*)1);    
  }    
W>void*fun2(void*arg)    
  {    
      for(int i='a';i<='z';i++)    
      {    
          printf("%c",i);    
          fflush(stdout);    
          usleep(100000);    
      }    
      pthread_exit((void*)2);    
  }    
  int main()    
  {    
      pthread_t tid1;                                                                                                                                                      
      pthread_t tid2;    
      
      pthread_create(&tid1,NULL,fun1,NULL);
      pthread_create(&tid2,NULL,fun2,NULL);    
     sleep(2);
      printf("\n");
      pthread_detach(tid1);
      pthread_detach(tid2);
      return 0;
  }

为了避免数据发生错误

对公共资源加锁,在该线程访问时,禁止其他进程使用,完成后解锁

互斥量

 pthread_mutex_init函数

#include <pthread.h>
​
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
    const pthread_mutexattr_t *restrict attr);
功能:
    初始化一个互斥锁。
参数:
    mutex:互斥锁地址。类型是 pthread_mutex_t 。
    attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
​
    可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:
    pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;
​
这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。
​
返回值:
    成功:0,成功申请的锁默认是打开的。
    失败:非 0 错误码

 pthread_mutex_lock函数


#include <pthread.h>
​
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
    对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非 0 错误码
​
int pthread_mutex_trylock(pthread_mutex_t *mutex);
   调用该函数时,若互斥锁未加锁,则上锁,返回 0;
   若互斥锁已加锁,则函数直接返回失败,即 EBUSY。

pthread_mutex_destroy函数 


#include <pthread.h>
​
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
    销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非 0 错误码

  pthread_mutex_unlock函数


#include <pthread.h>
​
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
    对指定的互斥锁解锁。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非0错误码

实例:


pthread_mutex_t mutex; //互斥锁
​
// 打印机
void printer(char *str)
{
    pthread_mutex_lock(&mutex); //上锁
    while (*str != '\0')
    {
        putchar(*str);
        fflush(stdout);
        str++;
        sleep(1);
    }
    printf("\n");
    pthread_mutex_unlock(&mutex); //解锁
}
​
// 线程一
void *thread_fun_1(void *arg)
{
    char *str = "hello";
    printer(str); //打印
}
​
// 线程二
void *thread_fun_2(void *arg)
{
    char *str = "world";
    printer(str); //打印
}
​
int main(void)
{
    pthread_t tid1, tid2;
​
    pthread_mutex_init(&mutex, NULL); //初始化互斥锁
​
    // 创建 2 个线程
    pthread_create(&tid1, NULL, thread_fun_1, NULL);
    pthread_create(&tid2, NULL, thread_fun_2, NULL);
​
    // 等待线程结束,回收其资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
​
    pthread_mutex_destroy(&mutex); //销毁互斥锁
​
    return 0;
}

死锁

 

 

1)什么是死锁?

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,永远在互相等待

2)死锁引起的原因?

  • 竞争不可抢占资源引起死锁
  • 竞争可消耗资源引起死锁
  • 进程推进顺序不当引起死锁

3)死锁的必要条件

  • 互斥条件:  某资源只能被一个进程使用,其他进程请求该资源时,只能等待,直到资源使用完毕后释放资源。
  • 请求和保持条件:  程序已经保持了至少一个资源,但是又提出了新要求,而这个资源被其他进程占用,自己占用资源却保持不放。
  • 不可抢占条件:  进程已获得的资源没有使用完,不能被抢占。
  • 循环等待条件:  必然存在一个循环链。

4)处理死锁的思路

  • 预防死锁

破坏死锁的四个必要条件中的一个或多个来预防死锁。

  • 避免死锁

和预防死锁的区别就是,在资源动态分配过程中,用某种方式防止系统进入不安全的状态。

  • 检测死锁

运行时出现死锁,能及时发现死锁,把程序解脱出来

  • 解除死锁

发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。

 5)预防死锁的方法

破坏请求和保持条件

协议1:

所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。

协议2:

允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。

破坏不可抢占条件

当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必须释放已经保持的所有资源,以后需要时再重新申请。

破坏循环等待条件

对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。


网站公告

今日签到

点亮在社区的每一天
去签到