信号量和互斥锁的区别

信号量和互斥锁都是用于多线程编程中的同步机制,但它们在用途和操作上存在一些差异。具体分析如下:

  1. 用途:互斥锁(Mutex)主要用于实现线程间的互斥,即确保同一时刻只有一个线程能够访问共享资源或临界区。它是用来保护单个资源的。信号量(Semaphore)则用于实现多个资源的同步,它可以控制对多个同类资源的访问,允许多个线程在同一时间内访问不同资源。
  2. 操作:互斥锁通常由一个线程获取并最终释放,其他试图获取已被锁定的互斥锁的线程将会被阻塞直到锁被释放。信号量可以由一个线程获取并由另一个线程释放,这使得它们在多线程间协调工作流时更加灵活。
  3. 计数器:互斥锁可以被看作是一种特殊类型的信号量,其计数器只有0和1两个值,而信号量的计数器可以是任意非负整数,表示可用资源的数量。

总的来说,信号量提供了更复杂的同步机制,适用于需要控制多个资源的并发访问的场景;而互斥锁则适用于保护单个资源,防止多线程同时访问造成数据不一致的问题。

互斥锁(Mutex)

互斥锁是一种用于防止多个线程同时访问共享资源的同步机制

互斥锁的主要目的是确保在任意时刻,只有一个线程可以进入被称为临界区的代码部分。临界区是指访问共享资源的部分代码,例如修改共享变量的代码段。互斥锁的使用可以避免因并发访问导致的数据不一致问题。

互斥锁的特点包括:

  • 排他性:当一个线程拥有互斥锁时,其他线程必须等待直到锁被释放。
  • 不可中断性:在某些实现中,如果一个线程已经获得了互斥锁,那么它自己不能再次获得这个锁,直到释放当前持有的锁。

此外,互斥锁通常由操作系统提供支持,其实现可能依赖于硬件的原子操作指令或软件算法。在多核处理器系统中,互斥锁的实现需要保证不同核心上的线程能够正确地竞争和释放锁。

需要注意的是,在使用互斥锁时,需要注意避免死锁和优先级反转等问题。死锁是指两个或多个线程互相等待对方释放锁,导致都无法继续执行的情况。优先级反转是指高优先级的线程因为等待低优先级线程持有的锁而被阻塞,导致系统效率降低。


互斥锁(Mutex)在C语言中是通过一系列函数来操作的,这些函数接口确保了多线程环境下对共享资源的安全访问。以下是互斥锁相关的函数接口详细介绍:

  1. 初始化互斥锁pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)

    • 参数mutex指向要初始化的互斥锁对象。
    • 参数attr指向互斥锁属性对象的指针,如果为NULL则使用默认属性。
    • 返回值:成功时返回0,错误时返回错误码。
  2. 销毁互斥锁pthread_mutex_destroy(pthread_mutex_t *mutex)

    • 参数mutex指向要销毁的互斥锁对象。
    • 无返回值。
  3. 锁定互斥锁pthread_mutex_lock(pthread_mutex_t *mutex)

    • 参数mutex指向要锁定的互斥锁对象。
    • 返回值:成功时返回0,错误时返回错误码。
  4. 解锁互斥锁pthread_mutex_unlock(pthread_mutex_t *mutex)

    • 参数mutex指向要解锁的互斥锁对象。
    • 返回值:成功时返回0,错误时返回错误码。
  5. 尝试锁定互斥锁pthread_mutex_trylock(pthread_mutex_t *mutex)

    • 参数mutex指向要尝试锁定的互斥锁对象。
    • 返回值:成功时返回0,如果互斥锁不可用则立即返回错误码。
  6. 设置互斥锁类型pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)

    • 参数attr指向互斥锁属性对象的指针。
    • 参数type指定互斥锁的类型,如PTHREAD_MUTEX_RECURSIVE、PTHREAD_MUTEX_ERRORCHECK等。
    • 返回值:成功时返回0,错误时返回错误码。
  7. 获取互斥锁类型pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type)

    • 参数attr指向互斥锁属性对象的指针。
    • 参数type用于存储获取到的互斥锁类型。
    • 返回值:成功时返回0,错误时返回错误码。
  8. 销毁互斥锁属性对象pthread_mutexattr_destroy(pthread_mutexattr_t *attr)

    • 参数attr指向要销毁的互斥锁属性对象。
    • 无返回值。

在使用这些函数时,需要注意以下几点:

  1. 确保每次锁定互斥锁后都要进行解锁,以避免死锁。
  2. 在持有互斥锁的时候,线程应尽可能快地释放锁,以减少其他线程等待的时间。
  3. 避免在持有互斥锁的情况下执行可能导致线程阻塞的操作,如I/O操作或系统调用,这可能会导致整个系统挂起。
  4. 当多个线程需要频繁访问共享资源时,可以考虑使用读写锁(如pthread_rwlock_t),它允许多个线程同时读取共享资源,但在写入时提供独占访问。

总之,互斥锁是多线程编程中非常重要的同步机制,通过上述函数接口可以有效地控制对共享资源的并发访问。


以下是一个简单的C语言代码示例,演示了如何使用互斥锁来保护共享资源:

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

// 定义一个全局变量作为共享资源
int shared_resource = 0;

// 定义一个互斥锁
pthread_mutex_t mutex;

// 线程函数,模拟对共享资源的访问
void *thread_func(void *arg) {
    // 获取互斥锁
    pthread_mutex_lock(&mutex);

    // 访问共享资源
    shared_resource++;
    printf("Thread %d: shared_resource = %d\n", (int)arg, shared_resource);

    // 释放互斥锁
    pthread_mutex_unlock(&mutex);

    return NULL;
}

int main() {
    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    // 创建两个线程
    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, thread_func, (void *)1);
    pthread_create(&thread2, NULL, thread_func, (void *)2);

    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);

    return 0;
}

在这个例子中,我们使用了一个全局变量shared_resource作为共享资源,并使用pthread_mutex_t类型的变量mutex作为互斥锁。在每个线程函数中,我们先通过pthread_mutex_lock函数获取互斥锁,然后访问共享资源,最后通过pthread_mutex_unlock函数释放互斥锁。这样可以确保在同一时刻只有一个线程可以访问共享资源,避免了数据竞争和不一致的问题。


信号量(Semaphore)

信号量是一种用于多线程编程中控制对共享资源访问的同步机制

信号量在多线程或多进程环境中提供了一种有效的访问控制手段,它允许一定数量的线程或进程同时访问某个资源。与互斥锁(Mutex)不同,互斥锁仅允许一个线程进入临界区,而信号量可以允许多个线程同时访问,但数量受限于信号量的计数器值。

以下是信号量的一些关键特点和用途:

  1. 信号量的基本概念:信号量维护了一个计数器,该计数器表示可用的资源数量。当线程需要访问共享资源时,必须首先尝试获取信号量。如果信号量的计数器大于零,线程就可以进入临界区并同时将计数器减一;如果计数器为零,则线程会等待直到有资源释放。
  2. 使用场景:信号量通常用于那些资源有明确访问数量限制的场景。例如,数据库连接池可能会限制同时进行连接的线程数量,以避免超出最大连接数造成的性能问题。在这种情况下,当达到限制数量后,后续的线程需要等待其他线程释放资源才能继续操作。
  3. 解决竞争条件:信号量可以用来解决互斥共享资源的同步问题。当并发进程竞争使用同一个资源时,通过信号量可以安排进程执行的先后顺序,确保每个进程都有一定的执行顺序,从而避免数据不一致等竞争条件发生。
  4. 提供灵活性:相比于互斥锁,信号量提供了更多的灵活性。在某些情况下,适当地使用信号量可以提高系统的性能和吞吐量,因为它允许多个线程并行执行,而不是互斥地串行执行。
  5. 实现细节:在Java中,Semaphore类提供了丰富的方法来控制信号量的行为,如acquire()用于获取许可,release()用于释放许可,还可以通过构造函数指定初始许可数量等。
  6. 注意事项:在使用信号量时,需要注意正确处理异常情况,确保在异常发生时释放已经获取的信号量许可,避免出现死锁。

综上所述,信号量是一种强大的同步工具,它不仅可以用于互斥访问控制,还可以用于更复杂的同步场景,如资源有限的情况下控制并发访问的数量。在实际应用中,合理使用信号量可以提高系统的并发性能和资源利用率。
sem_t 是一个用于表示信号量的自定义类型,通常在多线程编程中使用。信号量是一种同步机制,用于控制对共享资源的访问。


在C语言中,可以使用 <semaphore.h> 头文件中的函数来操作信号量。以下是一些常用的信号量操作函数:

  1. sem_init(sem_t *sem, int pshared, unsigned int value): 初始化一个信号量。

    • sem: 指向要初始化的信号量的指针。
    • pshared: 指定信号量是否为进程间共享。如果设置为0,则信号量仅在当前进程中可见;如果设置为非0值,则信号量在所有进程中都可见。
    • value: 设置信号量的初始值。
  2. sem_wait(sem_t *sem): 等待信号量。

    • sem: 指向要等待的信号量的指针。
    • 调用此函数会阻塞当前线程,直到信号量的值大于0。当信号量的值大于0时,将信号量的值减1,然后访问临界区资源。
  3. sem_post(sem_t *sem): 释放信号量。

    • sem: 指向要释放的信号量的指针。
    • 调用此函数会使信号量的值加1
  4. sem_destroy(sem_t *sem): 销毁信号量。

    • sem: 指向要销毁的信号量的指针。

以下是一个简单的C语言代码示例,演示了如何使用信号量来控制多线程访问共享资源:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdint.h>

//获取时间戳
uint64_t GetTickU64()
{
    struct timespec tTimeTick = {0};
    clock_gettime(CLOCK_MONOTONIC, &tTimeTick);
    return (uint64_t)(((uint64_t)tTimeTick.tv_sec * 1000000) + ((uint64_t)tTimeTick.tv_nsec / 1000));
}

sem_t sem; // 定义信号量
int count = 0;

void* increment(void* arg)
{
    for (int i = 0; i < 10; ++i) {
        sem_wait(&sem); // 等待信号量许可
        count++;
        usleep(10 * 1000); // 等待10毫秒
        sem_post(&sem); // 释放信号量许可
    }
    return NULL;
}

//测试信号量控制 多线程访问同一资源
void Test(unsigned int value)
{
    count = 0;
    sem_init(&sem, 0, value); // 初始化信号量,初始许可数量为value

    pthread_t t1, t2, t3;
    int threadIds[3] = {1, 2, 3};
    pthread_create(&t1, NULL, increment, &threadIds[0]);
    pthread_create(&t2, NULL, increment, &threadIds[1]);
    pthread_create(&t3, NULL, increment, &threadIds[2]);

    uint64_t qwStart = GetTickU64();//开始计时
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    printf("[value=%d] =====> Finally Count: %d  [cost: %lu us]\n", value, count, GetTickU64() - qwStart);

    sem_destroy(&sem); // 销毁信号量
}

int main()
{
	//测试信号量初始许可数量为1、2、3 时的多线程访问同一临界区的情况
    Test(1);
    Test(2);
    Test(3);
    
    return 0;
}

输出:

[value=1] =====> Finally Count: 30  [cost: 302613 us]
[value=2] =====> Finally Count: 30  [cost: 202500 us]
[value=3] =====> Finally Count: 30  [cost: 100897 us]

在这个例子中,我们创建了三个线程,每个线程都执行increment()函数来增加计数器count的值。同时我们使用了信号量sem来控制对count的访问。在每次访问count之前,线程需要调用sem_wait()等待信号量许可;在访问完成后,线程需要调用sem_post()释放信号量许可。

通过这种方式:

在信号量初始许可数量为1时,我们可以确保在同一时刻只有一个线程可以访问count
在信号量初始许可数量为2时,我们可以确保在同一时刻最多只有两个线程可以同时访问count
在信号量初始许可数量为2时,我们可以确保在同一时刻最多只有两个线程可以同时访问count

需要注意的是,在使用信号量时,需要正确处理异常情况,确保在异常发生时释放已经获取的信号量许可,避免出现死锁。此外,还需要在使用完信号量后调用sem_destroy()来销毁信号量,释放相关资源。

当信号量(Semaphore)的许可数量大于1时,它不是互斥的

信号量是一种用于多线程编程中控制对共享资源访问的同步机制。与互斥锁(Mutex)不同,信号量允许一定数量的线程同时进入临界区。
在您的示例中,当信号量的许可数量设置为3,这意味着最多有三个线程可以同时获得许可并执行任务中的代码。一旦达到最大许可数,其他尝试获取许可的线程将不得不等待,直到有许可被释放。这与互斥锁的全互斥性质不同,互斥锁在任何时刻只允许一个线程进入临界区。

综上所述,当信号量的许可数量为3时,它可以允许三个线程并发访问共享资源,而不是像互斥锁那样仅允许一个线程访问,因此它不是互斥的。


信号量是一个同步机制,它通过一个内部计数器来控制对共享资源的访问。当信号量的计数器值大于0时,表示有可用的资源,此时线程可以访问这些资源并将计数器减1。如果计数器值为0,则线程不能立即访问资源,可能需要等待直到计数器值再次变为正数。

信号量的初始计数器值确实反映了可同时访问资源的线程数量。如果计数器初始值设置为1,那么在任何时候只有一个线程能够访问被信号量保护的资源,这时信号量起到了互斥锁的作用。而如果初始值大于1,那么可能会有多个线程同时获得信号量并访问资源,但这取决于具体的应用场景和资源的共享方式。

需要注意的是,即使是初始计数器值大于1的信号量,也通常用于同步而非互斥。同步指的是协调不同线程或进程之间的执行顺序,确保它们按照既定的顺序进行协作;而互斥则是确保任何时刻只有一个线程或进程访问某个资源。


为了确保A、B、C三个线程按次序访问资源R,可以使用信号量来实现。下面是一个使用C语言的示例代码:

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

// 定义3个信号量
sem_t sem_A, sem_B, sem_C;

void* threadA(void* arg) {
    // 线程A需要访问资源R
    sem_wait(&sem_A);  // 等待信号量sem_A的值大于0
    printf("Thread A is accessing resource R.\n");
    // 执行对资源R的操作
    // ...
    sem_post(&sem_B);  // 释放信号量sem_B,增加计数器值
    return NULL;
}

void* threadB(void* arg) {
    // 线程B需要访问资源R
    sem_wait(&sem_B);  // 等待信号量sem_B的值大于0
    printf("Thread B is accessing resource R.\n");
    // 执行对资源R的操作
    // ...
    sem_post(&sem_C);  // 释放信号量sem_C,增加计数器值
    return NULL;
}

void* threadC(void* arg) {
    // 线程C需要访问资源R
    sem_wait(&sem_C);  // 等待信号量sem_C的值大于0
    printf("Thread C is accessing resource R.\n");
    // 执行对资源R的操作
    // ...
    sem_post(&sem_A);  // 释放信号量sem_A,增加计数器值
    return NULL;
}

int main() {
    // 初始化信号量
    sem_init(&sem_A, 0, 1);  // 初始计数器值为1,表示资源R可以被线程A访问
    sem_init(&sem_B, 0, 0);  // 初始计数器值为0,表示资源R暂时不能被线程B访问
    sem_init(&sem_C, 0, 0);  // 初始计数器值为0,表示资源R暂时不能被线程C访问

    // 创建线程A、B和C
    pthread_t tidA, tidB, tidC;
    pthread_create(&tidA, NULL, threadA, NULL);
    pthread_create(&tidB, NULL, threadB, NULL);
    pthread_create(&tidC, NULL, threadC, NULL);

    // 等待线程A、B和C结束
    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);
    pthread_join(tidC, NULL);

    // 销毁信号量
    sem_destroy(&sem_A);
    sem_destroy(&sem_B);
    sem_destroy(&sem_C);

    return 0;
}

在上述代码中,我们使用了三个信号量sem_Asem_Bsem_C来控制线程A、B和C对资源R的访问顺序。每个信号量的初始计数器值都设置为0,表示资源R暂时不能被对应的线程访问。只有当前一个线程完成对资源R的访问后,才会释放下一个信号量,从而允许下一个线程访问资源R。通过这种方式,我们可以确保A、B、C三个线程按次序访问资源R,并循环往复。


sem_timedwait 函数用于等待信号量,直到其值变为正数或达到指定的超时时间。

sem_timedwait是信号量操作函数中的一种,它允许线程在尝试获取信号量时设置一个时间限制。如果信号量的值为正,则线程可以立即继续执行并将信号量减1;如果信号量的值为0,则线程将阻塞,直到其他线程释放信号量或者达到了指定的超时时间。以下是sem_timedwait函数的详细介绍:

  • 函数签名

    #include <semaphore.h>
    int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict abs_timeout);
    
    • sem:指向要操作的信号量的指针。
    • abs_timeout:指向一个timespec结构体的指针,该结构体指定了绝对超时时刻。这个时刻由从Epoch(1970-01-01 00:00:00 +0000 (UTC))开始计算的秒数和纳秒数构成。
  • 返回值:成功时返回0,失败时返回-1并设置errno。如果超时发生,errno将被设置为ETIMEDOUT

在使用sem_timedwait时,需要注意以下几点:

  • 需要包含头文件<semaphore.h>
  • sem_wait不同,sem_timedwait提供了一个超时机制,可以在等待信号量时避免无限期的阻塞。
  • 如果调用时已经超出了指定的超时时刻,且信号量不能立即锁定,sem_timedwait将因超时而失败,并设置errnoETIMEDOUT
  • 如果操作能立即执行,sem_timedwait不会因超时而失败,无论abs_timeout的值如何。

总的来说,sem_timedwait函数提供了一种灵活的方式来处理信号量等待,使得线程能够在等待资源时考虑时间限制,从而避免可能的无限期等待问题。

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

sem_t sem;

void *thread_func(void *arg) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts); // 获取当前时间
    ts.tv_sec += 5; // 设置超时时间为5秒后

    int ret = sem_timedwait(&sem, &ts); // 等待信号量,直到超时或信号量可用
    if (ret == -1) {
        printf("Timeout!");
    } else {
        printf("Semaphore acquired!");
    }

    return NULL;
}

int main() {
    sem_init(&sem, 0, 0); // 初始化信号量为0(不可用)

    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL); // 创建线程

    sleep(2); // 主线程休眠2秒
    sem_post(&sem); // 释放信号量,使线程可以继续执行

    pthread_join(tid, NULL); // 等待线程结束

    sem_destroy(&sem); // 销毁信号量

    return 0;
}


sem_trywait 是信号量操作函数之一,用于尝试获取信号量而不阻塞线程。

sem_trywait函数的函数签名如下:

#include <semaphore.h>
int sem_trywait(sem_t *sem);
  • sem:指向要操作的信号量的指针。

该函数会立即尝试获取信号量,如果信号量的值为正数,则成功获取并返回0;如果信号量的值为0,则立即返回-1,表示无法获取到信号量。

使用sem_trywait时需要注意以下几点:

  • 需要包含头文件<semaphore.h>
  • 如果调用时信号量的值为0,即没有可用的资源,sem_trywait将立即返回-1,不会阻塞线程。
  • 如果调用时信号量的值大于0,即有可用的资源,sem_trywait将成功获取信号量并返回0。

总的来说,sem_trywait提供了一种非阻塞的方式来获取信号量,适用于需要在不阻塞线程的情况下尝试获取资源的场景。

sem_trywait 成功后会将信号量计数减1

sem_trywait函数在成功获取信号量时,即信号量的值大于0时,会执行递减操作,也就是将信号量的值减1。这与sem_wait函数的行为类似,不同之处在于sem_wait在信号量不可用(值为0)时会阻塞调用者,而sem_trywait则会立即返回错误,通常设置errnoEAGAIN

简单来说,无论是sem_wait还是sem_trywait,只要能够成功获取信号量(即信号量的值大于0),它们都会对信号量执行减1操作。这个操作有时也被称为“P操作”,是信号量控制中的一种基本操作,用于保证资源的互斥访问或同步不同线程或进程的操作。

异同

互斥和同步是并发编程中的两个基本概念,它们用来确保多线程或多进程环境中对共享资源的访问不会导致数据不一致或其他问题。具体来说:

  • 互斥(Mutual Exclusion):指的是当一个线程或进程正在使用某个共享资源(如变量、文件、硬件设备等)时,其他线程或进程必须等待,直到该资源被释放。互斥保证了在同一时刻,只有一个线程或进程可以进入被称为临界区(Critical Section)的代码段。例如,当一个进程在使用打印机时,其他进程不能同时使用打印机,必须等待直到打印机被释放。
  • 同步(Synchronization):是在互斥的基础上实现的一种更复杂的控制机制。它不仅要求在访问共享资源时互相排斥,还要求按照某种特定的顺序来执行线程或进程中的操作。同步通常涉及到一些协调机制,如信号量、锁、条件变量等,以确保多个线程或进程能够有序地访问资源,并且正确地协同工作以完成复杂的任务。

综上所述,互斥主要是为了防止多个线程同时访问共享资源造成的问题,而同步则是为了保证线程之间的操作能够以一种预期的顺序执行,从而协调它们的行为,确保程序的正确性和数据的一致性。


信号量和互斥锁在多线程编程中都是重要的同步机制,但它们在用途、操作以及计数器方面上是有区别的。

  1. 用途:互斥锁(Mutex)主要用于实现线程间的互斥,确保同一时刻只有一个线程能够访问共享资源或临界区。它是用于保护单个资源的。而信号量(Semaphore)则用于实现多个资源的同步,它可以控制对多个同类资源的访问,允许多个线程在同一时间内访问不同资源。
  2. 操作:互斥锁通常由一个线程获取并最终释放,其他试图获取已被锁定的互斥锁的线程将会被阻塞直到锁被释放。信号量则可以由一个线程获取并由另一个线程释放,这使得它们在多线程间协调工作流时更加灵活。
  3. 计数器:互斥锁可以被看作是一种特殊类型的信号量,其计数器只有0和1两个值,而信号量的计数器可以是任意非负整数,表示可用资源的数量。

总的来说,信号量提供了更复杂的同步机制,适用于需要控制多个资源的并发访问的场景;而互斥锁则适用于保护单个资源,防止多线程同时访问造成数据不一致的问题。


互斥锁和信号量在处理并发问题时的主要区别在于它们的目的和使用方式。具体分析如下:

  • 目的:互斥锁主要用于实现线程的互斥,即确保某一资源同时只允许一个访问者对其进行访问。信号量则用于实现线程的同步,它不仅保证了互斥访问,还能通过控制许可的数量来限制同时访问资源的线程数,从而实现对资源访问顺序的控制。
  • 使用方式:互斥锁的值只能为0或1,适用于单一资源的互斥访问场景。而信号量的值可以为非负整数,可以用于多个同类资源的多线程互斥和同步。当信号量为单值时,它可以像互斥锁一样完成一个资源的互斥访问。

总的来说,互斥锁更多地被用于保护临界区资源,以防止多个线程同时修改同一数据,而信号量则用于更复杂的同步场景,如控制对有限数量资源的访问或维护线程间的执行顺序。

PS

1. pthread_create 是一个用于创建线程的函数。

它的原型如下:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数说明:

  • thread:指向一个pthread_t类型的指针,用于存储新创建线程的ID。
  • attr:指向一个pthread_attr_t类型的指针,用于设置线程的属性。如果设置为NULL,则使用默认属性。
  • start_routine:指向一个函数指针,该函数将在新创建的线程中执行。
  • arg:传递给start_routine函数的参数。

返回值:

  • 成功时,返回0;
  • 失败时,返回错误码。

2. pthread_join 是一个用于等待线程结束的函数,它的作用是阻塞当前线程,直到指定的线程执行完毕。

它的原型如下:

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

参数说明:

  • thread:需要等待的线程ID。
  • retval:指向一个指针,用于存储被等待线程的返回值。如果不关心返回值,可以设置为NULL。

返回值:

  • 成功时,返回0;
  • 失败时,返回错误码。

使用示例:

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

void *print_hello(void *arg) {
    printf("Hello from thread!");
    return NULL;
}

int main() {
    pthread_t thread;
    int ret;

    // 创建线程
    ret = pthread_create(&thread, NULL, print_hello, NULL);
    if (ret != 0) {
        printf("Error creating thread!");
        return 1;
    }

    // 等待线程结束
    ret = pthread_join(thread, NULL);
    if (ret != 0) {
        printf("Error joining thread!");
        return 1;
    }

    printf("Thread has finished!");
    return 0;
}

Sleep 函数用于暂停程序执行一段时间。

Sleep 函数是编程中常用的一个函数,它可以让当前正在运行的程序(进程、任务或线程)进入休眠状态,即在一段时间内不执行任何操作。这个函数通常用于需要暂停程序执行的场景,比如等待某个事件发生或是为了同步多个线程的执行进度。

在不同的编程环境中,Sleep 函数的使用方式可能有所不同:

  • Windows系统:在 Windows 系统中,Sleep 函数的参数通常以毫秒为单位。例如,Sleep(1000); 会使程序暂停1秒钟。在 Visual C++ 中,Sleep 函数的第一个字母通常是大写的"S",即Sleep
  • 标准C语言:在标准 C 语言中,Sleep 函数的第一个字母是小写的"s",即sleep,并且参数通常以秒为单位。

此外,在使用 Sleep 函数时,需要注意以下几点:

  • 大小写问题:不同的编译器对于 Sleep 函数的大小写要求可能不同。在 Visual C++ 中,通常使用大写的Sleep,而在其他环境下,如标准 C,则使用小写的sleep
  • 非活动状态:当程序调用 Sleep 函数时,它会进入非活动状态,直到指定的时间过去。在这个时间内,程序不会消耗CPU资源,但也不会执行任何操作。
  • 计时器到期:Sleep 函数的计时是从函数被调用的那一刻开始的。一旦计时器到期,程序将继续执行。但是,如果在此期间接收到信号或程序发生中断,也可能导致程序继续执行。
  • 参数单位:在大多数情况下,sleep 函数的参数代表暂停的时间长度。在Windows系统中,这个时间通常以毫秒为单位,而在Linux系统下则以秒为单位。
  • 头文件引用:要使用 sleep 函数,通常需要包含特定的头文件。在 Windows 下,你需要包含 <windows.h>,而在 Linux 下,则需要包含 <unistd.h>
  • 作用效果:当调用 sleep 函数时,当前线程会被暂停指定的时间,期间线程不会执行任何操作。这可以用于模拟耗时操作,或者在多线程编程中同步各个线程的执行进度。
  • 注意事项:在使用 sleep 函数时,需要注意其区分大小写,有的编译器要求大写(如 Sleep),有的则要求小写(如 sleep)。此外,调用 sleep 函数会阻塞当前线程,如果需要非阻塞性的延迟,可以考虑使用其他非阻塞性的方法。

总的来说,了解 Sleep 函数的工作原理和使用方法对于编写需要精确控制执行流程的程序来说非常重要。


errno 是一个全局变量,用于存储系统调用或库函数的错误代码。

当一个函数发生错误时,它会设置errno变量的值来表示具体的错误类型。在C语言中,可以通过检查errno的值来确定发生了哪种错误。例如:

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main() {
    FILE *file = fopen("nonexistent_file.txt", "r");
    if (file == NULL) {
        printf("打开文件失败,错误代码:%d,错误信息:%s", errno, strerror(errno));
    } else {
        fclose(file);
    }
    return 0;
}

在这个例子中,我们尝试打开一个不存在的文件,fopen函数会返回NULL,并将errno设置为相应的错误代码。然后我们使用strerror函数将错误代码转换为可读的错误信息。


timespec 是C语言标准库中定义的一个结构体,用于表示时间。

在C语言的标准库中,time.h头文件提供了多个与时间相关的函数和数据类型。timespec结构体就是其中之一,它用于高精度的时间表示,包括秒和纳秒两个部分。这允许程序进行精细的时间测量和时间操作。

timespec结构体通常与clock_gettime函数一起使用,该函数能够获取当前时钟的时间,并将其存储在timespec结构体中。这个结构体的定义如下:

struct timespec {
    time_t   tv_sec;  /* 秒 */
    long     tv_nsec; /* 纳秒 */
};
  • tv_sec字段是一个time_t类型的成员,代表自1970年1月1日(UNIX纪元)以来的秒数。
  • tv_nsec字段是一个long类型的成员,代表纳秒数,与tv_sec结合使用,可以提供高达纳秒级别的时间精度。

在C11标准(ISO / IEC 9899:2011)中,timespec结构体被正式定义,并且在time.h头文件中进行声明。这意味着任何遵循C11或更高版本的C语言标准实现都应该支持timespec结构体。

需要注意的是,在Linux编程中,timespec结构体通常与clock_gettime函数一起使用,该函数可以获取特定时钟的时间,并将其存储在timespec结构体中。CLOCK_REALTIME是常用的时钟类型之一,它表示当前的墙上时间。

总的来说,timespec是C语言标准库中的一个重要组成部分,它为程序员提供了一个精确的时间表示方法,特别是在需要高精度时间测量的场景中。

CLOCK_MONOTONICCLOCK_REALTIME 是Linux系统中两种不同的时钟类型,它们在计时方面有一些不同的特点。

  1. CLOCK_MONOTONIC:这是一个不可调整的时钟,它从某个固定点开始计时,不会因为系统时间的改变而改变。它主要用于测量时间间隔,因为它不受系统时间的影响。它的精度通常比CLOCK_REALTIME高,但可能受到系统负载等因素的影响。

  2. CLOCK_REALTIME:这是一个可调整的时钟,它表示的是实际的墙上时钟时间。当系统时间被修改时,它也会相应地改变。它的精度通常较低,但可以用于测量实际经过的时间。

在使用clock_gettime函数获取时间时,可以选择使用这两种时钟类型之一。例如:

#include <time.h>
#include <stdio.h>

int main() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    printf("CLOCK_MONOTONIC: %ld.%09ld seconds since the Epoch", ts.tv_sec, ts.tv_nsec);

    clock_gettime(CLOCK_REALTIME, &ts);
    printf("CLOCK_REALTIME: %ld.%09ld seconds since the Epoch", ts.tv_sec, ts.tv_nsec);

    return 0;
}

这段代码分别获取了CLOCK_MONOTONICCLOCK_REALTIME两种时钟类型的当前时间,并将其打印出来。

CLOCK_MONOTONIC是从系统启动时刻开始计时的

CLOCK_MONOTONIC是一种计时方式,它从系统启动的那一刻起开始计算时间,并且不受系统时间被用户改变的影响。即使用户更改了系统时间,CLOCK_MONOTONIC也不会受到影响,因为它是单调递增的,只与系统启动后经过的时间有关。

CLOCK_MONOTONIC与gettimeofday函数之间的关系是,它们都可以用来获取当前时间,但CLOCK_MONOTONIC提供的是从系统启动到现在的单调时间,而gettimeofday返回的是自1970年1月1日以来的实时时间(墙上时间)。

在编写需要精确时间测量的代码时,选择使用CLOCK_MONOTONIC可以确保时间的测量不受系统时间更改的影响,从而提供更加稳定和可靠的时间基准。


gettimeofday是一个用于获取当前时间的函数,它返回自1970年1月1日以来的秒数和微秒数。

该函数的原型如下:

#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);

其中,tv参数指向一个timeval结构体,用于存储当前时间;tz参数指向一个timezone结构体,用于存储时区信息。如果不需要时区信息,可以将tz设置为NULL。

gettimeofday函数的返回值为0表示成功,-1表示失败。

在Linux/Unix环境下,gettimeofday是常用的计时函数之一,通常用来获取程序运行时间或进行时间相关的测量。而在Windows系统中,由于没有这个函数,开发者通常会选择其他方式来实现类似的功能。例如,可以使用Windows API提供的GetTickCount()、timeGetTime()或QueryPerformanceCounter()等函数来获取时间。

使用示例:

#include <stdio.h>
#include <sys/time.h>

int main() {
    struct timeval tv;
    if (gettimeofday(&tv, NULL) == -1) {
        perror("gettimeofday failed");
        return 1;
    }
    printf("seconds: %ld, microseconds: %ld", tv.tv_sec, tv.tv_usec);
    return 0;
}

输出结果类似于:

seconds: 1628453600, microseconds: 123456

timeval是一个用于表示时间的结构体,它包含两个成员:tv_sec和tv_usec。

  • tv_sec:这个字段的类型是time_t,它代表自1970年1月1日(UNIX纪元)以来的秒数。这个字段通常用来存储时间的整数秒部分。
  • tv_usec:这个字段的类型是suseconds_t,它代表微秒数,即秒后面的小数部分,以百万分之一秒为单位。这个字段用来存储时间的精确到微秒的部分。

需要注意的是,在C语言中,要使用timeval结构体,需要包含头文件sys/time.h。此外,timeval结构体通常与gettimeofday函数一起使用,该函数可以将当前时间以timeval结构体的形式返回,从而提供精确到微秒的时间信息。

  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值