Linux多线程编程(6.4)

线程:轻量级的进程,是调度的最小单位,
线程是共享同一进程地址空间多个可独立被调度运行的任务


一 多线程  与 多进程的区别 


1.在一个进程中创建的多个线程,共享同一个进程的资源,各线程独立被内核调度 
2.多个进程是独立地址空间


相同点:
1.都参与统一的调度 
2.都有自己的ID,一组寄存器的值  


不同点:线程间共享统一个进程的地址空间 ,进程间是独立地址空间 


二 线程间共享资源和私有资源 


共享资源:全局变量,打开文件获得文件描述符 
私有资源:私有TID,私有栈,errno


所有的程序都有一个主线程(main thread),主线程是进程的控制流或执行线程。
在多线程程序中,主线程可以创建一个或多个对等线程(peer thread),
从这个创建时间点开始,这些线程就开始并发执行。
主线程和对等线程的区别其中之一是,主线程总是进程中第一个运行的线程。


三 线程的操作 
NPTL 线程库中函数大多以pthread_开头,线程库中函数大都返回errno而不是置errno


调用NPTL中函数需要加上 pthread.h头文件
编译和链接需加上 -pthread


1.创建一个线程
 
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
void * ( *start_routine ) (void *), void *arg);


功能:创建一个线程 
参数:
    thread          获得线程ID 
    attr            线程的属性,通常为NULL表示使用默认属性
    start_routine   线程的执行函数,即线程执行的代码,采用回调方式执行线程函数
    arg             给线程函数传递的参数 
返回值:
成功返回0,失败返回错误码


注意:pthread_t为unsigned long类型

例:

/*void *thread_func(void *arg);*/
void *thread_func(void *arg)
{
    int *pval = (int *)arg;
    int val = *(int *)arg;
}

pthread_t tid;
int val = 10;
char buf[N] = "hello world";
int ret = 0;

ret = pthread_create(&tid,NULL,thread_func,&val);
if(ret != 0)
{
    /*errno = ret;*/
    /*perror();*/
    fprintf(stderr,"pthread_create : %s\n",strerror(errno));
    exit(EXIT_FAILURE);
}
//建议用法
#define handle_error_en(en, msg) \
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
---------------------------------------------------------------------
void *thread_func(void *arg)
{
    char *p = (char *)arg;
}

s = pthread_create(&thr, NULL, &thread_func, buf);
if (s != 0)
    handle_error_en(s, "pthread_create");

pthread_t pthread_self(void);
//功能:获得当前线程的tid ,类似进程中的getpid(2),该函数总是成功
练习:创建两个线程,线程1不传参,线程1每秒循环打印自己的PID和TID,死循环;
线程2传主线程中变量int val;的值,线程2 每秒打印并将*arg参数加1,打印10次
主线程创建两个线程,之后循环5次打印线程1和线程2的TID,和val值,每秒一次


2.线程退出
void pthread_exit(void *retval);
功能:结束当前线程的执行 


参数:
    retval  给等待的线程带回一个地址值 ,如果没有值带回,写为NULL 


返回值:无


3.线程连接
等待线程退出,作用类似进程中的wait(2)系统调用,可实现线程同步
int pthread_join(pthread_t thread, void **retval);
功能:阻塞方式等待指定的线程退出,并且释放结束线程未释放的资源(例如:线程的私有栈资源等)


参数:
    thread  线程TID 
    retval  获得pthread_exit带回的地址值  


返回值:
成功返回0,失败返回错误码


注意:如果等待的线程没有退出,则调用pthread_join的线程会阻塞


例:

    //thread1 中调用pthread_exit();
    //thread2 中调用pthread_join();

    thread1 :tid1
    1.
    static int val;
    val = *(int *)arg;

    pthread_exit(&val);
    return &val;
    2.
    int *pval = (int *)malloc(sizeof(int));
    pthread_exit(pval);
//===================================================
    thread2 :tid2
    int *retval = NULL;

    pthread_join(tid1,(void **)&retval);
     *retval <=>  val
4.线程分离。
通知系统当线程结束,可以自动回收线程未释放的资源 
如果线程没有终止,pthread_detach()函数也不会令其终止。
int pthread_detach(pthread_t thread);


5.请求取消一个线程执行 
int pthread_cancel(pthread_t thread);


练习:
1.打开一个文件 test.txt
2.创建两个线程,线程1向文件中写入数据,数据从键盘输入,
线程2从文件中读出数据,向屏幕打印
3.循环读写,当线程1读到"quit"时,写入文件并结束,线程2读到文件中"quit"结束
4.主线程等待两线程退出
//提示:
//打开文件获得文件描述符
int fd = open();

//创建线程传文件描述符
pthread_create(&tid1,NULL,write_file,&fd);

//线程函数接收参数
void *write_file(void *arg)
{
    int fd = *(int *)arg;
}

//主线程等待两线程退出
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);

#include 
#include 
#include 
#include 
#include 
//return errno
#define handle_error_en(en, msg) \
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
//set errno
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)



void *thread_fun1(void *arg)
{
    static int val = 10;
    
   while(1)
   {
       printf("child thread-1 pid = %d tid = %lu\n",getpid(),pthread_self());
       sleep(1);
   //    pthread_exit(&val);
         pthread_exit("i am died\n");
   }
}

void *thread_fun2(void *arg)
{
   int n = 10;
   while(n--)
   {
       printf("child-2 argv = %d\n",(*(int*)arg)++);
       sleep(1);
       //exit(0); 
       //pthread_exit(0);
       //pthread_exit(NULL);
       //return ;
   }
}

int main(int argc, const char *argv[])
{
   pthread_t tid1,tid2;
   int n = 0;
   int val = 0;
   int i = 5;
   int *retval = NULL;
   char *s = NULL;

   if ((n = pthread_create(&tid1,NULL,thread_fun1,NULL)) != 0)
   {
      handle_error_en(n,"pthread_create");
   }
   
   if ((n = pthread_create(&tid2,NULL,thread_fun2,&val)) != 0)
   {
      handle_error_en(n,"pthread_create");
   }
   
   
   while(i--)
   {
       printf(" main thread val = %d tid1 = %lu tid2 = %lu\n",val,tid1,tid2);
       sleep(1);
       //exit(0); 
       //pthread_join(NULL,&retval);
       pthread_cancel(tid2);
   
   }

   //pthread_join(tid1,(void **)&retval);
   pthread_join(tid1,(void **)&s);
   //printf("child thread 1 exit status %d\n",*retval);
   printf("I know on my god! : %s\n",s);
    return 0;
}


四 线程间的机制


1.线程同步   


同步:多个线程按照约定的顺序相互配合完成一件事情 


POSIX 线程间同步机制:无名信号量 


信号量 :它代表一类资源,其值代表资源的个数,是一个受保护的变量,不能直接赋值 
只有三种操作方法:初始化信号量,p操作,v操作,应该成对使用!


用sem_t类型描述信号量


(1)定义一个信号量 


sem_t  read_sem;
sem_t  write_sem;


(2)初始化信号量 
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化指定的信号量 
参数:
    sem  信号量的地址 
    pshared  0:线程间使用  非0:进程间使用 
    value   初始化的值 
返回值:成功返回0;失败返回-1,并置errno


例:封装初始化操作

int init_sem(sem_t *sem,unsigned int value)
{
    if(sem_init(sem,0,value) < 0)
    {
        perror("sem_init");
        exit(EXIT_FAILURE);
    }

    return 0;
}
(3)P操作 : 申请资源,可能会引起调用线程阻塞 


int sem_wait(sem_t *sem);
功能:申请资源,如果没有资源,则引起调用者阻塞 /*{{{*/
参数:
    sem  信号量的地址 
返回值:
成功返回0,失败返回-1,并置errno /*}}}*/


例:P操作函数

int P(sem_t *sem)
{
    if(sem_wait(sem) < 0)
    {
        perror("sem_wait");
        exit(EXIT_FAILURE);
    }

    return 0;
}
(4)V操作 : 释放资源 
int sem_post(sem_t *sem);
功能:释放资源,如果有等待资源的线程,则唤醒第一个等待的线程 /*{{{*/
参数:
    sem  信号量的地址 
返回值:
成功返回0,失败返回-1 /*}}}*/


例:V操作函数
int V(sem_t *sem)
{
    if(sem_post(sem) < 0)
    {
        perror("sem_post");
        exit(EXIT_FAILURE);
    }

    return 0;
}
2.线程间互斥 


互斥:保证某一个时刻只有一个线程访问资源。 


POSIX 线程间互斥机制:互斥锁,保证操作临界资源完整性,防止竞争
用pthread_mutex_t 类型描述互斥锁


(1).临界资源 : 一段时间内只允许有一个任务访问,应该加锁保护 
(2).临界区   : 访问临界资源的一段代码,即影响共享数据的代码段 


互斥锁操作:
(1)定义互斥锁 


pthread_mutex_t lock;


(2)初始化互斥锁


//动态初始化:指定锁的属性
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);


//静态初始化:锁只能使用默认的属性,注意只能在定义时使用!
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


(3)申请互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
特点:如果互斥锁被别的线程的使用,则阻塞调用者


(4)释放互斥锁 
int pthread_mutex_unlock(pthread_mutex_t *mutex);


(5)销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);




*3.线程间的条件变量
条件变量可以使线程睡眠等待某种条件出现。


条件变量是利用线程间共享的全局变量进行同步的一种机制,
主要包括两个动作:一个线程等待"条件变量的条件成立"而阻塞等待
另一个线程使"条件成立"(给出条件成立信号),从而唤醒等待的线程.


为了防止竞争,条件变量总是和一个互斥锁结合在一起使用。


一般说来,条件变量被用来进行线程间的同步。
条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况下,
从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,提高效率


用pthread_cond_t类型描述条件变量


(1)定义一个条件变量
pthread_cond_t cond;


(2)初始化条件变量
//动态初始化 
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
//静态初始化 
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;


(3)让线程等待一个条件
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
功能:等待一个条件 
参数:
    cond 等待的条件
    mutex 互斥锁 
返回值:
成功返回0, 失败返回错误码 


注意: 
pthread_cond_wait 内部实现:
1.前一次调用,条件不满足,释放锁,然后阻塞
2.后一次调用,条件满足,获得锁,然后返回


(4)唤醒等待条件线程 
//唤醒等待队列中所有等待该条件的线程
int pthread_cond_broadcast(pthread_cond_t *cond);


//唤醒第一个等待条件的线程
int pthread_cond_signal(pthread_cond_t *cond);

你可能感兴趣的:(Linux进程开发)