二、多线程_Linux C

  • Post author:
  • Post category:linux




二、多线程_Linux C



线程的概念

概念理解线程可以理解为一个正在运行的函数。

posix线程是一套标准,而不是实现。(常用标准)

openmp线程标准。

线程标识:

pthread_t

(Linux下就是一个整型数)

在posix标准下线程相关的命名都是以

pthread_

开头的。

  • pthread_equal 判断两个线程号是否相等,相等返回非0值,否则返回0。
NAME
       pthread_equal - compare thread IDs

SYNOPSIS
       #include <pthread.h>

       int pthread_equal(pthread_t t1, pthread_t t2);

       Compile and link with -pthread.

DESCRIPTION
       The pthread_equal() function compares two thread identifiers.

RETURN VALUE
       If the two thread IDs are equal, pthread_equal() returns a nonzero value; otherwise, it returns 0.
  • pthread_self 返回调用函数的进程号
NAME
       pthread_self - obtain ID of the calling thread

SYNOPSIS
       #include <pthread.h>

       pthread_t pthread_self(void);

       Compile and link with -pthread.

DESCRIPTION
       The  pthread_self() function returns the ID of the calling thread.  This is the same value that is returned in
       *thread in the pthread_create(3) call that created this thread.

RETURN VALUE
       This function always succeeds, returning the calling thread's ID.



线程的创建

  • pthread_create 创建一个线程,线程的调度取决于调度器的调度策略。
NAME
       pthread_create - create a new thread

SYNOPSIS
       #include <pthread.h>
	   /**参数说明
	    * @param thread	回填的线程标识
	    * @param attr	线程属性
	    * @param start_routine	线程执行的函数
	    * @param arg	start_routine的参数
	    */
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);/
		//成功返回 0, 失败直接返回 error number!!!
       Compile and link with -pthread.
//创建一个线程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>


void *func(void *p){
    puts("working.............");

    return NULL;
}


int main(){


    pthread_t tid;
    int err;
    puts("Begin!");

    err = pthread_create(&tid, NULL, func, NULL);

    printf("%s\n", strerror(err));

    puts("End!");
    return 0;
}



线程的终止

线程的3种终止方式

1)线程从启动例程返回,线程返回值就是线程的退出码。

2)线程可以被同一进程中的其它线程取消。

3)线程调用

pthread_exit()

函数。

  • pthread_exit 线程终止函数
NAME
       pthread_exit - terminate calling thread

SYNOPSIS
       #include <pthread.h>

       void pthread_exit(void *retval);

       Compile and link with -pthread.

DESCRIPTION
       The  pthread_exit()  function terminates the calling thread and returns a value via retval that (if the thread
       is joinable) is available to another thread in the same process that calls pthread_join(3).

       Any clean-up handlers established by pthread_cleanup_push(3) that have not yet been popped, are popped (in the reverse  of  the  order  in which they were pushed) and executed.  If the thread has any thread-specific data, then, after the clean-up handlers have been executed, the corresponding destructor functions are called, in an unspecified order.

       When  a  thread terminates, process-shared resources (e.g., mutexes, condition variables, semaphores, and file descriptors) are not released, and functions registered using atexit(3) are not called.

       After the last thread in a process terminates, the process terminates as by calling exit(3) with an exit  status of zero; thus, process-shared resources are released and functions registered using atexit(3) are called.

RETURN VALUE
       This function does not return to the caller.
  • pthread_join 进程资源回收
NAME
       pthread_join - join with a terminated thread

SYNOPSIS
       #include <pthread.h>
		/*
			thread		需要回收的线程标识
			*retval		线程状态  
		*/
       int pthread_join(pthread_t thread, void **retval);

       Compile and link with -pthread.

DESCRIPTION
       The pthread_join() function waits for the thread specified by thread to terminate.  If that thread has already terminated, then pthread_join() returns immediately.  The thread specified by thread must be joinable.

       If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the  value  that the  target  thread supplied to pthread_exit(3)) into the location pointed to by retval.  If the target thread was canceled, then PTHREAD_CANCELED is placed in the location pointed to by retval.

       If multiple threads simultaneously try to join with the same thread, the results are undefined.  If the thread calling  pthread_join()  is  canceled,  then  the  target  thread  will  remain joinable (i.e., it will not be detached).

RETURN VALUE
       On success, pthread_join() returns 0; on error, it returns an error number.
  • 线程栈清理

    • pthread_cleanup_push
    • pthread_cleanup_pop
pthread_cleanup_push//相当于挂钩子函数
pthread_cleanup_pop//相当于取钩子函数
//这两个实现不是函数 而是宏,上面的两个函数必须成对出现不然会出现大括号不匹配的错误
NAME
       pthread_cleanup_push, pthread_cleanup_pop - push and pop thread cancellation clean-up handlers

SYNOPSIS
       #include <pthread.h>
		//routine	处理函数
    	//arg		处理函数接收的参数
       void pthread_cleanup_push(void (*routine)(void *),
                                 void *arg);
		//execute 1代表执行 0代表不执行
       void pthread_cleanup_pop(int execute);

       Compile and link with -pthread.

DESCRIPTION
       These  functions  manipulate  the calling thread's stack of thread-cancellation clean-up handlers.  A clean-up handler is a function that is automatically executed when a thread is canceled (or in  various  other  circumstances  described below); it might, for example, unlock a mutex so that it becomes available to other threads、 in the process.

       The pthread_cleanup_push() function pushes routine onto the top of the stack of clean-up handlers.  When  routine is later invoked, it will be given arg as its argument.

       The  pthread_cleanup_pop()  function  removes  the  routine  at the top of the stack of clean-up handlers, andoptionally executes it if execute is nonzero.

       A cancellation clean-up handler is popped from the stack and executed in the following circumstances:

       1. When a thread is canceled, all of the stacked clean-up handlers are popped and executed in the  reverse  of the order in which they were pushed onto the stack.

       2. When a thread terminates by calling pthread_exit(3), all clean-up handlers are executed as described in the preceding point.  (Clean-up handlers are not called if the thread terminates by performing  a  return  from the thread start function.)
                                 void *arg);
       void pthread_cleanup_pop(int execute);

       Compile and link with -pthread.

DESCRIPTION
       These  functions  manipulate  the calling thread's stack of thread-cancellation clean-up handlers.  A clean-up handler is a function that is automatically executed when a thread is canceled (or in  various  other  circumstances  described below); it might, for example, unlock a mutex so that it becomes available to other threads in the process.
       handler is a function that is automatically executed when a thread is canceled (or in  various  other  circumstances  described below); it might, for example, unlock a mutex so that it becomes available to other threads in the process.

       The pthread_cleanup_push() function pushes routine onto the top of the stack of clean-up handlers.  When  routine is later invoked, it will be given arg as its argument.

       The  pthread_cleanup_pop()  function  removes  the  routine  at the top of the stack of clean-up handlers, and optionally executes it if execute is nonzero.

       A cancellation clean-up handler is popped from the stack and executed in the following circumstances:

       1. When a thread is canceled, all of the stacked clean-up handlers are popped and executed in the  reverse  of the order in which they were pushed onto the stack.

       2. When a thread terminates by calling pthread_exit(3), all clean-up handlers are executed as described in the preceding point.  (Clean-up handlers are not called if the thread terminates by performing  a  return  from the thread start function.)

       3. When a thread calls pthread_cleanup_pop() with a nonzero execute argument, the top-most clean-up handler is popped and executed.
  • 线程的取消

一个线程在执行的过程中经常会使用进程的取消。

pthread_cancel

//取消进程 先用 cancel 再用 join

NAME
       pthread_cancel - send a cancellation request to a thread
//取消有两种状态:允许 和 不允许
    //允许取消又分为 异步取消 和 推迟取消
    //推迟取消 是默认的 是指推迟到cancel点执行
    //cancel点:posix定义的cancel点都是可能引发阻塞的系统调用
SYNOPSIS
       #include <pthread.h>

       int pthread_cancel(pthread_t thread);

       Compile and link with -pthread.

RETURN VALUE
       On success, pthread_cancel() returns 0; on error, it returns a nonzero error number.     

pthread_setcancelstate 设置是否允许取消

pthread_setcanceltype 设置取消方式

SYNOPSIS
       #include <pthread.h>

       int pthread_setcancelstate(int state, int *oldstate);
       int pthread_setcanceltype(int type, int *oldtype);

       Compile and link with -pthread.

pthread_testcancel 什么都不做,就是一个取消点。

NAME
       pthread_testcancel - request delivery of any pending cancellation request

SYNOPSIS
       #include <pthread.h>

       void pthread_testcancel(void);

       Compile and link with -pthread.

DESCRIPTION
       Calling  pthread_testcancel() creates a cancellation point within the calling thread, so that a thread that is otherwise executing code that contains no cancellation points will respond to a cancellation request.

       If cancelability is disabled (using pthread_setcancelstate(3)), or no cancellation request is pending, then  a call to pthread_testcancel() has no effect.

RETURN VALUE
       This  function  does not return a value.  If the calling thread is canceled as a consequence of a call to this function, then the function does not return.
  • 线程分离

pthread_detach 已经detach的线程无法被jion回来。

NAME
       pthread_detach - detach a thread

SYNOPSIS
       #include <pthread.h>

       int pthread_detach(pthread_t thread);

       Compile and link with -pthread.

DESCRIPTION
       The  pthread_detach() function marks the thread identified by thread as detached.  When a detached thread terminates, its resources are automatically released back to the system without the need for  another  thread  to join with the terminated thread.

       Attempting to detach an already detached thread results in unspecified behavior.

RETURN VALUE
       On success, pthread_detach() returns 0; on error, it returns an error number.

使用示例:查找质数

/* 线程改良 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

#define LEFT    30000000
#define RIGHT   30000200
#define THRNUM  (RIGHT - LEFT + 1)

struct thr_arg_st {
    int n;
};


void *thread_prime(void *p){
  
    int i, j, mark;
    i = ((struct thr_arg_st *)p)->n;
    //free(p);
    mark = 1;
    for(j = 2; j < i/2; j++){
        if(i % j == 0){
            mark = 0;
            break;
        }
    }
    if(mark)
        printf("%d is Primeer!\n", i);
    pthread_exit(p);

}

int main(){

    /* 找质数 */

    int i, err;
    struct thr_arg_st *p;
    void *ptr;

    //创建线程
    pthread_t tid[THRNUM];
    for(i = LEFT; i <= RIGHT; i++){
        //设置参数
       p = malloc(sizeof(*p));
       p->n = i;
        //创建线程并传入参数
       err = pthread_create(tid+(i - LEFT), NULL, thread_prime,p);
       if(err){
           exit(1);
       }
    }

    //回收线程
    for(i = LEFT; i <= RIGHT; i++){

        //接返回的指针并释放空间
        pthread_join(tid[i - LEFT], &ptr);
        free(ptr);
    }

    exit(0);
}
//存在问题 应该限制线程数



线程同步



互斥量:

1)pthread_mutex_init

2)pthread_mutex_destroy

NAME
       pthread_mutex_destroy, pthread_mutex_init — destroy and initialize a mutex

SYNOPSIS
       #include <pthread.h>

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       int pthread_mutex_init(pthread_mutex_t *restrict mutex,
           const pthread_mutexattr_t *restrict attr);
       pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
NAME
       pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock — lock and unlock a mutex

SYNOPSIS
       #include <pthread.h>

       int pthread_mutex_lock(pthread_mutex_t *mutex);
       int pthread_mutex_trylock(pthread_mutex_t *mutex);
       int pthread_mutex_unlock(pthread_mutex_t *mutex);

DESCRIPTION
       The  mutex  object  referenced by mutex shall be locked by a call to pthread_mutex_lock() that returns zero or [EOWNERDEAD].  If the mutex is already locked by another thread, the calling  thread  shall  block  until  the mutex  becomes  available. This operation shall return with the mutex object referenced by mutex in the locked state with the calling thread as its owner. If a thread attempts to relock a mutex that it has already locked, pthread_mutex_lock()  shall  behave  as  described  in  the  Relock column of the following table. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked,  pthread_mutex_unlock()  shall behave as described in the Unlock When Not Owner column of the following table.

例程:

/*线程文件读写竞争*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#define THRNUM      20
#define PATH        "out"
#define BUFSIZE     1024

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

static void* pth_action(void *p){
    FILE *fp;
    char buf[BUFSIZE];

    fp = fopen(PATH, "r+");
    if(fp == NULL){
        fprintf(stderr, "fopen\n");
        exit(1);
    }

    //锁的实现是限制代码段的执行而不是限制资源的使用
    //临界区前加锁
    pthread_mutex_lock(&mutex);
    fgets(buf, BUFSIZE, fp);
    
    fseek(fp, 0, SEEK_SET);
    fprintf(fp, "%d\n", atoi(buf) + 1);
    fflush(fp);
    释放锁
    pthread_mutex_unlock(&mutex);
    fclose(fp);

    pthread_exit(NULL);

}

int main(){

    pthread_t pth[THRNUM];
    int err;

    for(int i = 0;i < THRNUM; i++){
        err = pthread_create(pth+i, NULL, pth_action, NULL);
        if(err){
            fprintf(stderr, "create:%s\n", strerror(err));
            exit(1);
        }
    }

    for(int i = 0;i < THRNUM; i++){
        pthread_join(pth[i], NULL);
    }

    pthread_mutex_destroy(&mutex);
    exit(0);
}

练习:有四个线程,拼命的分别往终端上输出a b c d ,希望能够有规律的输出abcd

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#define THRNUM  4

pthread_mutex_t mutex[THRNUM];

static void *pthread_action(void* p){
    int n = (int)p;
    int c = 'a' + (int)p;
    while (1){
        pthread_mutex_lock(mutex + n);
        write(1,&c,1);
        pthread_mutex_unlock(mutex + (n+1)%THRNUM);
    }
    pthread_exit(NULL);
    
}

int main(){

    pthread_t pth[THRNUM];

    for(int i = 0; i < THRNUM; i++){
        pthread_mutex_init(mutex + i, NULL);
        pthread_mutex_lock(mutex+i);

        pthread_create(pth+i, NULL, pthread_action, (void*)i);
    }
    pthread_mutex_unlock(mutex);

    alarm(5);
    for(int i = 0; i < THRNUM; i++){
        pthread_join(pth[i], NULL);
    }
    exit(0);
}

线程的池类(非标准线程池写法)任务池

查找质数

//设置一个全局变量 num 发放任务
//num 大于0 待计算任务
//num 等于0 上游需要发放 下游需要等待
//num 等于-1 无任务
/* 线程 任务池 */
//存在忙等
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

#define LEFT    30000000
#define RIGHT   30000200
#define THRNUM  4

int num = 0;
pthread_mutex_t mutx = PTHREAD_MUTEX_INITIALIZER;

struct thr_arg_st {
    int n;
};


void *thread_prime(void *p){
  
    int i, j, mark;
    int k = ((struct thr_arg_st*)p)->n;
    while(1){
        pthread_mutex_lock(&mutx);
        
        while(num == 0){
            pthread_mutex_unlock(&mutx);
            sched_yield();//针对调度器的函数  出让调度器给其他线程
            pthread_mutex_lock(&mutx);
            
        }

        i = num;
        if(num == -1){
            pthread_mutex_unlock(&mutx);
            break;//注意临界区内所有的跳转语句 如果向外跳转需要解锁
        }
        num = 0;
        pthread_mutex_unlock(&mutx);
        
        mark = 1;
        for(j = 2; j < i/2; j++){
            if(i % j == 0){
                mark = 0;
                break;
            }
        }
        if(mark)
            printf("[%d]:%d is Primeer!\n", k, i);

    }
    pthread_exit(p);

}

int main(){

    /* 找质数 */

    int i, err;
    struct thr_arg_st *p;
    void *ptr;

    //创建线程
    pthread_t tid[THRNUM];
    for(i = 0; i <= THRNUM; i++){
        //设置参数
        p = malloc(sizeof(*p));
        p->n = i;
        
        //创建线程并传入参数
        err = pthread_create(tid+i, NULL, thread_prime, p);
        if(err){
            exit(1);
        }
    }

    for(int j = LEFT; j <= RIGHT; j++){
        pthread_mutex_lock(&mutx);
        while(num != 0){
            pthread_mutex_unlock(&mutx);
            sched_yield();//针对调度器的函数  出让调度器给其他线程
            pthread_mutex_lock(&mutx);
            
        }
        num = j;
        pthread_mutex_unlock(&mutx);
    }

    pthread_mutex_lock(&mutx);
    while(num != 0){
        pthread_mutex_unlock(&mutx);
        sched_yield();//针对调度器的函数  出让调度器给其他线程
        pthread_mutex_lock(&mutx);
        
    }
    num = -1;
    pthread_mutex_unlock(&mutx);

    //回收线程
    for(i = 0; i <= THRNUM; i++){

        //接返回的指针并释放空间
        pthread_join(tid[i], &ptr);
        free(ptr);
    }
    pthread_mutex_destroy(&mutx);
    exit(0);
}
  • 动态模块的单次初始化
NAME
       pthread_once - once-only initialization

SYNOPSIS
       #include <pthread.h>
		//保证 once_control这个函数只被调用一次
       pthread_once_t once_control = PTHREAD_ONCE_INIT;

       int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));

DESCRIPTION
       The  purpose  of  pthread_once  is to ensure that a piece of initialization code is executed at most once. The once_control argument points to a static or  extern  variable  statically  initialized to PTHREAD_ONCE_INIT.

       The first time pthread_once is called with a given once_control argument, it calls init_routine with no argument and changes the value of the once_control variable to  record  that  initialization  has been performed. Subsequent calls to pthread_once with the same once_control argument do nothing.



条件变量:

类型:pthread_cond_t

函数:pthread_cond_init

​ … …

SYNOPSIS
       #include <pthread.h>

       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

       int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

		//只叫醒一个
       int pthread_cond_signal(pthread_cond_t *cond);

		//所有都叫醒
       int pthread_cond_broadcast(pthread_cond_t *cond);
		
		//死等
       int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
		
		//设置时间等待
       int pthread_cond_timedwait(pthread_cond_t *cond, 
                  pthread_mutex_t *mutex, const struct timespec  *abstime);

       int pthread_cond_destroy(pthread_cond_t *cond);

DESCRIPTION
	   pthread_cond_init initializes the condition variable cond, using the condition attributes  specified in  cond_attr,  or default attributes if cond_attr is NULL. The LinuxThreads implementation supports no attributes for conditions, hence the cond_attr parameter is actually ignored.

       Variables  of  type  pthread_cond_t  can  also  be  initialized  statically,  using  the  constant PTHREAD_COND_INITIALIZER.

	   pthread_cond_signal  restarts one of the threads that are waiting on the condition variable cond. If no threads are waiting on cond, nothing happens. If several threads are waiting on cond, exactly one is restarted, but it is not specified which.

       pthread_cond_broadcast  restarts  all  the  threads that are waiting on the condition variable cond. Nothing happens if no threads are waiting on cond.

       pthread_cond_wait atomically unlocks the mutex (as per pthread_unlock_mutex) and waits for the  condition  variable cond to be signaled. The thread execution is suspended and does not consume any CPU time until the condition variable is signaled. The mutex must be locked by the calling thread on entrance  to  pthread_cond_wait. Before returning to the calling thread, pthread_cond_wait re-acquires  mutex (as per pthread_lock_mutex).

       Unlocking the mutex and suspending on the condition  variable  is  done  atomically.  Thus,  if  all threads  always acquire the mutex before signaling the condition, this guarantees that the condition cannot be signaled (and thus ignored) between the time a thread locks the  mutex  and  the time it waits on the condition variable.
           
       pthread_cond_timedwait atomically unlocks mutex and waits on cond, as pthread_cond_wait does, but it also bounds the duration of the wait. If cond has not been signaled within the amount of time specified by abstime, the mutex mutex is re-acquired and pthread_cond_timedwait returns the error ETIMEDOUT.  The abstime parameter specifies an absolute time, with the same origin as time(2) and gettimeofday(2): an abstime of 0 corresponds to 00:00:00 GMT, January 1, 1970.

       pthread_cond_destroy  destroys a condition variable, freeing the resources it might hold. No threads must be waiting on the condition variable on entrance to pthread_cond_destroy. In  the  LinuxThreads implementation,  no resources are associated with condition variables, thus pthread_cond_destroy actually does nothing except checking that the condition has no waiting threads.

CANCELLATION
       pthread_cond_wait and pthread_cond_timedwait are cancellation points. If a thread is cancelled while suspended  in one of these functions, the thread immediately resumes execution, then locks again the mutex argument to pthread_cond_wait and pthread_cond_timedwait, and finally executes  the  cancellation.  Consequently, cleanup handlers are assured that mutex is locked when they are called.

例程:pthr_pool_cond.c

/* 线程r任务池求质数 */
//通知法 无忙等
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

#define LEFT    30000000
#define RIGHT   30000200
#define THRNUM  4

int num = 0;
pthread_mutex_t mutx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_num = PTHREAD_COND_INITIALIZER;

struct thr_arg_st {
    int n;
};


void *thread_prime(void *p){
  
    int i, j, mark;
    int k = ((struct thr_arg_st*)p)->n;
    while(1){
        pthread_mutex_lock(&mutx);
        
        while(num == 0){
            pthread_cond_wait(&cond_num, &mutx);
        }

        i = num;
        if(num == -1){
            pthread_mutex_unlock(&mutx);
            break;//注意临界区内所有的跳转语句 如果向外跳转需要解锁
        }
        num = 0;
        pthread_cond_broadcast(&cond_num);
        pthread_mutex_unlock(&mutx);
        
        mark = 1;
        for(j = 2; j < i/2; j++){
            if(i % j == 0){
                mark = 0;
                break;
            }
        }
        if(mark)
            printf("[%d]:%d is Primeer!\n", k, i);

    }
    pthread_exit(p);

}

int main(){

    /* 找质数 */

    int i, err;
    struct thr_arg_st *p;
    void *ptr;

    //创建线程
    pthread_t tid[THRNUM];
    for(i = 0; i <= THRNUM; i++){
        //设置参数
        p = malloc(sizeof(*p));
        p->n = i;
        
        //创建线程并传入参数
        err = pthread_create(tid+i, NULL, thread_prime, p);
        if(err){
            exit(1);
        }
    }

    for(int j = LEFT; j <= RIGHT; j++){
        pthread_mutex_lock(&mutx);
        while(num != 0){
            pthread_cond_wait(&cond_num, &mutx);
        }
        num = j;
        pthread_cond_signal(&cond_num);//叫醒一个就行
        pthread_mutex_unlock(&mutx);
    }

    pthread_mutex_lock(&mutx);
    while(num != 0){
        pthread_cond_wait(&cond_num, &mutx);
    }
    num = -1;
    pthread_cond_broadcast(&cond_num);
    pthread_mutex_unlock(&mutx);

    //回收线程


    for(int i = 0; i <= THRNUM; i++){
        //接返回的指针并释放空间
        pthread_join(tid[i], &ptr);
        free(ptr);
    }

    pthread_mutex_destroy(&mutx);
    pthread_cond_destroy(&cond_num);
    exit(0);
}



信号量:

初始化一个资源总量,每有一个任务减一,直至资源耗尽。使用完归还资源。

可以用 互斥量 + 信号量 解决。

这里我们用之前同时用200个线程计算30000000~30000200之间的质数程序为例,我们在不改变整个程序的结构的前提下,限制同时存在的线程数量(4个)来保证计算机资源能够合理分配理用。

下面的 mysem就是简易的信号量封装程序

makefile

CFLAGS+=-pthread
LDFLAGS+=-pthread

target:mysem

mysem: pthr.o mysem.o
	$(CC) $^ -o $@ $(CFLAGS) $(LDFLAGS)

clean:
	$(RM) -rf *.o mysem

mysem.h

/**
 * @file mysem.h
 * @brief 信号量的实现 互斥 + 信号量的方法实现
*/

#ifndef MYSEM_H__
#define MYSEM_H__


#define MYSEM_MAXSIZE   512 //信号量资源上限 初始化
#define MYSEM_MINSIZE   1   //信号量资源下限 初始化


typedef void  mysem_t;      // 信号量结构体

/**
 * @name mysem_init
 * @brief 初始化信号量
 * @param totalsize 信号量总资源
 * @return 信号量结构体指针 -succee | NULL - faild
 * 
*/
mysem_t *mysem_init(int totalsize);

/**
 * @name mysem_add
 * @brief 添加信号量
 * @param mysem 信号量结构体指针
 * @param n 归还的信号量资源数
 * @return 暂时默认成功
 * 
*/
int mysem_add(mysem_t *mysem, int n);

/**
 * @name mysem_sub
 * @brief 索取信号量
 * @param mysem 信号量结构体指针
 * @param n 索取的信号量资源数
 * @return 暂时默认成功
 * 
*/
int mysem_sub(mysem_t *mysem, int n);

/**
 * @name mysem_destroy
 * @brief 销毁信号量结构体
 * @param mysem 信号量结构体指针
 * @return 默认成功
 * 
*/
void mysem_destroy(mysem_t *mysem);

#endif

mysem.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include "mysem.h"

/* 信号量结构体 */
struct mysem_st{
    int size;       //信号量总资源
    int token;      //剩余信号量资源
    pthread_mutex_t mut;    //资源锁
    pthread_cond_t cond;    //条件变量
};



mysem_t *mysem_init(int totalsize){

    /// 判断输入是否合法
    if(totalsize < MYSEM_MINSIZE || totalsize > MYSEM_MAXSIZE){
        return NULL;
    }

    /// 指针分配内存
    struct mysem_st *me = malloc(sizeof(*me));
    if(me == NULL){
        return NULL;
    }

    /// 信号量结构体成员变量初始化
    me->size = totalsize;
    me->token = totalsize;
    pthread_mutex_init(&me->mut, NULL);
    pthread_cond_init(&me->cond, NULL);

    /// 返回信号量结构体指针
    return me;
}

int mysem_add(mysem_t *mysem, int n){
    /// 判断输入是否合法
    if(n <= 0 || n > MYSEM_MAXSIZE){
        fprintf(stderr, "Size of n is illegal!\n");
        exit(1);
    }

    /// 指针类型强转
    struct mysem_st* me = mysem;

    /// 加锁
    pthread_mutex_lock(&me->mut);

    /// 归还信号量资源
    me->token += n;

    /// 唤醒阻塞等待 惊群
    pthread_cond_broadcast(&me->cond);

    /// 解锁
    pthread_mutex_unlock(&me->mut);

    return 0;
}

int mysem_sub(mysem_t *mysem, int n){
    /// 判断输入是否合法
    if(n <= 0 || n > MYSEM_MAXSIZE){
    fprintf(stderr, "Size of n is illegal!\n");
    exit(1);
    }
    /// 指针类型强转
    struct mysem_st* me = mysem;

    /// 加锁
    pthread_mutex_lock(&me->mut);
    /// 判断资源数是否充足
    while(me->token < n){
        /// 阻塞等待条件变量唤醒
        pthread_cond_wait(&me->cond, &me->mut);
    }
    /// 索取资源
    me->token -= n;
    /// 解锁
    pthread_mutex_unlock(&me->mut);

    return 0;

}

void mysem_destroy(mysem_t *mysem){
    /// 指针类型强转
    struct mysem_st *me = mysem;
    if(mysem == NULL){
        return ;
    }
    /// 销毁 锁
    pthread_mutex_destroy(&me->mut);
    /// 销毁条件变量
    pthread_cond_destroy(&me->cond);
    /// 释放结构体指针指向的空间
    free(me);
    
    mysem = NULL;
}

主函数 pthr.c

/* 线程改良 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

#include "mysem.h"

#define LEFT    30000000
#define RIGHT   30000200
#define THRNUM  (RIGHT - LEFT + 1)
#define N   4
mysem_t *mysem;
struct thr_arg_st {
    int n;
};


void *thread_prime(void *p){
  
    int i, j, mark;
    
    i = ((struct thr_arg_st *)p)->n;
    
    mark = 1;

    for(j = 2; j < i/2; j++){
        if(i % j == 0){
            mark = 0;
            break;
        }
    }
    if(mark)
        printf("%d is Primeer!\n", i);

    //这里加了一个5s延时方便观察线程的数量
    sleep(5); //ps ax -L 
    
    
    //还信号
    mysem_add(mysem, 1);
    pthread_exit(p);

}
int main(){

    /* 找质数 */

    int i, err;
    struct thr_arg_st *p;


    void *ptr;
    mysem = mysem_init(N);

    //创建线程数组
    pthread_t tid[THRNUM];

    for(i = LEFT; i <= RIGHT; i++){
        
        p = malloc(sizeof(*p));
        p->n = i;
        //要信号
        mysem_sub(mysem, 1);
        //创建线程
        err = pthread_create(tid+(i - LEFT), NULL, thread_prime,p);
        if(err){
           exit(1);
        }
    }

    //回收线程
    for(i = LEFT; i <= RIGHT; i++){

        //接返回的指针并释放空间
        pthread_join(tid[i - LEFT], &ptr);
        free(ptr);
    }

    mysem_destroy(mysem);
    exit(0);
}




读写锁:

  • 读锁:允许多个人同时读
  • 写锁:只允许一个人写

实现问题:容易出现写者饿死的现象,源源不断地有读者来读文件,写者就会产生饥饿。

相当于 共享锁 + 互斥锁。读锁 -> 共享锁 写锁->互斥锁。

实现 略



线程属性

线程属性类型:pthread_attr_t

函数:pthread_attr_init()

SYNOPSIS
       #include <pthread.h>

       int pthread_attr_init(pthread_attr_t *attr);
       int pthread_attr_destroy(pthread_attr_t *attr);

       Compile and link with -pthread.

SEE ALSO
       pthread_attr_setaffinity_np(3), pthread_attr_setdetachstate(3), 
       pthread_attr_setguardsize(3), pthread_attr_setinheritsched(3), 
       pthread_attr_setschedparam(3), pthread_attr_setschedpolicy(3), 
       pthread_attr_setscope(3), pthread_attr_setstack(3), 
       pthread_attr_setstackaddr(3), pthread_attr_setstacksize(3), 
       pthread_create(3), pthread_getattr_np(3), 
       pthread_setattr_default_np(3), pthreads(7)

man手册示例

#include <stdio.h>
#include <stdlib.h>

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

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

static void
display_pthread_attr(pthread_attr_t *attr, char *prefix)
{
    int s, i;
    size_t v;
    void *stkaddr;
    struct sched_param sp;

    s = pthread_attr_getdetachstate(attr, &i);
    if (s != 0)
        handle_error_en(s, "pthread_attr_getdetachstate");

    printf("%sDetach state        = %s\n", prefix,
            (i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :
            (i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :
            "???");

    s = pthread_attr_getscope(attr, &i);
    if (s != 0)
        handle_error_en(s, "pthread_attr_getscope");
    printf("%sScope               = %s\n", prefix,
            (i == PTHREAD_SCOPE_SYSTEM)  ? "PTHREAD_SCOPE_SYSTEM" :
            (i == PTHREAD_SCOPE_PROCESS) ? "PTHREAD_SCOPE_PROCESS" :
            "???");

    s = pthread_attr_getinheritsched(attr, &i);
    if (s != 0)
        handle_error_en(s, "pthread_attr_getinheritsched");
    printf("%sInherit scheduler   = %s\n", prefix,
            (i == PTHREAD_INHERIT_SCHED)  ? "PTHREAD_INHERIT_SCHED" :
            (i == PTHREAD_EXPLICIT_SCHED) ? "PTHREAD_EXPLICIT_SCHED" :
            "???");

    s = pthread_attr_getschedpolicy(attr, &i);
    if (s != 0)
        handle_error_en(s, "pthread_attr_getschedpolicy");
    printf("%sScheduling policy   = %s\n", prefix,
            (i == SCHED_OTHER) ? "SCHED_OTHER" :
            (i == SCHED_FIFO)  ? "SCHED_FIFO" :
            (i == SCHED_RR)    ? "SCHED_RR" :
            "???");

    s = pthread_attr_getschedparam(attr, &sp);
    if (s != 0)
        handle_error_en(s, "pthread_attr_getschedparam");
    printf("%sScheduling priority = %d\n", prefix, sp.sched_priority);

    s = pthread_attr_getguardsize(attr, &v);
    if (s != 0)
        handle_error_en(s, "pthread_attr_getguardsize");
    printf("%sGuard size          = %d bytes\n", prefix, v);

    s = pthread_attr_getstack(attr, &stkaddr, &v);
    if (s != 0)
        handle_error_en(s, "pthread_attr_getstack");
    printf("%sStack address       = %p\n", prefix, stkaddr);
    printf("%sStack size          = 0x%zx bytes\n", prefix, v);
}

static void *thread_start(void *arg)
{
    int s;
    pthread_attr_t gattr;

    /* pthread_getattr_np() is a non-standard GNU extension that
        retrieves the attributes of the thread specified in its
        first argument */

    s = pthread_getattr_np(pthread_self(), &gattr);
    if (s != 0)
        handle_error_en(s, "pthread_getattr_np");

    printf("Thread attributes:\n");
    display_pthread_attr(&gattr, "\t");

    exit(EXIT_SUCCESS);         /* Terminate all threads */
}

int main(int argc, char *argv[])
{
    pthread_t thr;
    pthread_attr_t attr;
    pthread_attr_t *attrp;      /* NULL or &attr */
    int s;

    attrp = NULL;

    /* If a command-line argument was supplied, use it to set the
        stack-size attribute and set a few other thread attributes,
        and set attrp pointing to thread attributes object */

    if (argc > 1) {
        int stack_size;
        void *sp;

        attrp = &attr;

        s = pthread_attr_init(&attr);
        if (s != 0)
            handle_error_en(s, "pthread_attr_init");

        s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        if (s != 0)
            handle_error_en(s, "pthread_attr_setdetachstate");

        s = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
        if (s != 0)
            handle_error_en(s, "pthread_attr_setinheritsched");

        stack_size = strtoul(argv[1], NULL, 0);

        s = posix_memalign(&sp, sysconf(_SC_PAGESIZE), stack_size);
        if (s != 0)
            handle_error_en(s, "posix_memalign");

        printf("posix_memalign() allocated at %p\n", sp);

        s = pthread_attr_setstack(&attr, sp, stack_size);
        if (s != 0)
            handle_error_en(s, "pthread_attr_setstack");
    }

    s = pthread_create(&thr, attrp, &thread_start, NULL);
    if (s != 0)
        handle_error_en(s, "pthread_create");

    if (attrp != NULL) {
        s = pthread_attr_destroy(attrp);
        if (s != 0)
            handle_error_en(s, "pthread_attr_destroy");
    }

    pause();    /* Terminates when other thread calls exit() */
}




重入

本身大多数的库函数都支持多线程并发,不会出现重入现象(POSIX规定)。

例如大多数标准的IO函数都是支持多线程并发,实现机制大概是,在读写时先锁定缓冲区,操作后解锁,但是也有unlock版本供单线程使用。



线程的信号

每个线程都有自己的mask位图和pending位图,而进程只有pending位图,没有mask位图。

如果一个进程给另一个进程发送信号(进程间的信号传输)就会体现在进程的pending位上;

如果一个线程给另一个线程发送信号(线程间的信号传输)就会体现在线程的pending位上;


如何判断那个线程响应信号?这就要看当前信号响应的时候是哪一个线程从kernel态转换成user态,当前被调度的线程会用自己的mask位与进程的pending位做一个按位与看看以进程为单位收到的是那个信号,然后再用自己的mask和pending做按位与判断当前线程收到的是那个信号。

  • 相关函数

pthread_sigmask

NAME
       pthread_sigmask - examine and change mask of blocked signals

SYNOPSIS
       #include <signal.h>

       int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

       Compile and link with -pthread.

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       pthread_sigmask():
           _POSIX_C_SOURCE >= 199506L || _XOPEN_SOURCE >= 500

DESCRIPTION
       The pthread_sigmask() function is just like sigprocmask(2), with 
       the difference that its use in multithreaded programs is explicitly 
       specified by POSIX.1.  Other differences are noted in this page.

       For a description of the arguments and operation of this function, 
       see sigprocmask(2).

sigwait

NAME
       sigwait - wait for a signal

SYNOPSIS
       #include <signal.h>

        int sigwait(const sigset_t *set, int *sig);


DESCRIPTION
       The  sigwait()  function suspends execution of the calling thread 
       until one of the signals specified in the signal set set becomes 
       pending.  The function accepts the signal (removes it from the 
       pending list of signals), and returns the signal number in sig.

       The operation of sigwait() is the same as sigwaitinfo(2), except 
       that:

       * sigwait() returns only the signal number, rather than a siginfo_t 
         structure describing the signal.

       * The return values of the two functions are different.

pthread_kill

NAME
       pthread_kill - send a signal to a thread

SYNOPSIS
       #include <signal.h>

       int pthread_kill(pthread_t thread, int sig);

       Compile and link with -pthread.
DESCRIPTION
       The  pthread_kill()  function  sends  the  signal sig to thread, 
       a thread in the same process as thecaller.  The signal is 
       asynchronously directed to thread.

       If sig is 0, then no signal is sent, but error checking is still 
       performed.



线程与fork

POSIX 规定fork出来的进程只包含调用fork的一个线程;

DCB 规定fork出来的进程会与原进程完全相同。

openmp线程标准

提供语法标记让编译器了解并发标识,可跨语言,严重依赖编译器



版权声明:本文为baidu_39049318原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。