c语言多线程pthread的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c语言多线程pthread的问题相关的知识,希望对你有一定的参考价值。

给一个例子 并说说在vc中怎么配置

运行之前需要做一些配置:1.下载PTHREAD的WINDOWS开发包 pthreads-w32-2-4-0-release.exe(任何一个版本均可) http://sourceware.org/pthreads-win32/ ,解压到一个目录。2.找到include和lib文件夹,下面分别把它们添加到VC++6.0的头文件路径和静态链接库路径下面: a).Tools->Options,选择Directory页面,然后在Show directories for:中选择Include files(默认), 在Directories中添加include的路径。在Show directories for:中选择Library files, 在Directories中添加lib的路径。 b).Project->Settings,选择Link页面,然后将lib下的*.lib文件添加到Object/library Modules, 各lib文件以空格隔开。 c).将lib下的*.dll文件复制到工程目录下,即根目录。 如果不配置环境,将出现以下错误:Linking...
工程名.obj : error LNK2001: unresolved external symbol __imp__pthread_join
工程名.obj : error LNK2001: unresolved external symbol __imp__pthread_create
Debug/工程名.exe : fatal error LNK1120: 2 unresolved externals
Error executing link.exe. 我自己以前编的一个例子,火车票窗口卖票的 如果不加锁就会出现-2,-1的问题 这里只是个事例 #include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <windows.h>int piao = 100; void* tprocess1(void* args)

while(piao>0)
Sleep(1);
piao--;
printf("窗口1----------------还剩%d张票\n",piao);
// Sleep(5);

return NULL;

void* tprocess2(void* args)

while(piao>0)
Sleep(1);
printf("窗口2----------------还剩%d张票\n",piao);
// Sleep(9);

return NULL;

void* tprocess3(void* args)

while(piao>0)
Sleep(1);
piao--;
printf("窗口3----------------还剩%d张票\n",piao);
// Sleep(10);

return NULL;

void* tprocess4(void* args)

while(piao>0)
Sleep(1);
piao--;
printf("窗口4----------------还剩%d张票\n",piao);
// Sleep(10);

return NULL;

int main()
pthread_t t1;
pthread_t t2;
pthread_t t3;
pthread_t t4;
pthread_create(&t1,NULL,tprocess1,NULL);
pthread_create(&t2,NULL,tprocess2,NULL);
pthread_create(&t3,NULL,tprocess3,NULL);
pthread_create(&t4,NULL,tprocess4,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
参考技术A 建议您到文库找一下pthread库里面的一些函数的说明,不是三言两语就能说完了。您要先确定自己要实现的目的,不要为了用线程而用线程。

C 语言编程 — pthread 线程操作

目录

文章目录

用户线程

User Thread 是由 User Process 负责创建、调度和销毁的线程,运行在 User Space 中,Kernel 对其不感知也不管理。

一个 User Process 可以拥有多个 User Threads,且至少拥有一个 Main Thread(初始线程)。Main Thread 是 User Process 的第一个 Thread(TID 为 1),并且本质也是一个 User Thread,用于从 main() 开始执行 User Process 自身的程序代码。

当开发者通过 pthread 库创建了多个 User Threads 后,这些 User Threads 就会共享同一个 User Process 的资源。对于 User Thread 而言,它只具备在 CPU 上执行所必需的资源,例如:PC(程序计数器)、Registers(寄存器)和 Stack(栈)。所以 User Thread 非常的轻量,适用于高并发的场景。

多线程的切换

由于 User Thread 不被 Kernel 感知,不参与直接的 CPU 调度,但 User Thread 同样存在在同一个 CPU 上的切换行为。即:同一个 User Process 中的多个User Threads 只会在 User Process 所调度的 CPU 上进行线程切换。

而 User Thread 的切换完全是 User Process 决定的,也就是由开发者实现的代码逻辑来控制的,所以通常称之为 “协作式调度”,即:多个 User Thread 之间自行协商让出 CPU。所以,User Thread 的协作式调度不存在 CPU 在用户模式和内核模式之间的切换消耗。

pthread 线程库

在 pthread 中,使用 TCB(Thread Control Block,线程控制块)来存储 User Thread 的所有信息,TCB 的体量会比 PCB 小很多。对应的 pthread 结构体如下:

// pthread/pthread_impl.h

struct pthread 
    struct pthread *self;           // 指向自身的指针
    struct __pthread_internal_list *thread_list;  // 线程列表,指向线程列表的指针,用于实现线程池;
    void *(*start_routine)(void*);  // 线程的入口函数,由 pthread_create() 函数传入;
    void *arg;                      // 线程的入口函数参数,由 pthread_create() 函数传入;
    void *result;                   // 线程的返回值,由线程的入口函数返回;
    pthread_attr_t *attr;           // 线程的属性,包括栈保护区大小、调度策略等,由 pthread_create() 函数传入;
    pid_t tid;                      // 线程的唯一标识符,由 Kernel 分配;
    struct timespec *waiters;       // 等待的时间戳
    size_t guardsize;               // 栈保护区大小
    int sched_policy;               // 调度策略
    struct sched_param sched_params;  // 调度参数
    void *specific_1stblock;        // 线程私有数据的第一个块
    struct __pthread_internal_slist __cleanup_stack;  // 清理函数栈
    struct __pthread_mutex_s *mutex_list;  // 线程持有的互斥锁列表
    struct __pthread_cond_s *cond_list;    // 线程等待的条件变量列表
    unsigned int detach_state:2;     // 线程分离状态,包括分离和未分离两种;
    unsigned int sched_priority:30;  // 线程的调度优先级
    unsigned int errno_val;          // 线程的错误码
;

pthread 库,围绕 struct pthread 提供了一系列的接口,用于完成 User Thread 管理。

线程的创建和销毁

pthread_create()

函数作用:用于创建一条新的线程,并指定线程的入口函数和参数。

函数原型

  • thread 参数:是一个 pthread_t 类型指针,用于存储 TID。TID 由 Kernel 随机分配。
  • attr 参数:是一个 pthread_attr_t 类型指针,用于指定线程的属性,包括指定栈保护区大小、调度策略、优先级等,通常为 NULL。
  • start_routine 参数:线程入口函数,是一个 void* 类型函数指针(或直接使用函数名)。线程入口函数必须是一个 static 静态函数或全局函数,因为 pthread 会把线程入口函数的返回值传递到 pthread_join() 中,所以需要能够找到它。
  • arg 参数:线程参数,是一个 void* 类型参数。
  • 函数返回值
    • 成功:返回 0;
    • 失败:返回 -1;
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

Main/Parent Thread 调用 pthread_create() 后,pthread 库就会为 Child Thread 分配 TCB、PC(程序计数器)、Registers(寄存器)和 Stack(栈)等资源。随后 pthread 库会初始化 TCB,并将其加入到 Thread Pool 中等待执行。直到 User Thread 被调度到 CPU 时,开始执行线程入口函数。

pthread_join()

函数作用:如果 Parent Thread 希望获得 Child Thread 的执行结果,可以通过 pthread_join() 来实现,用于等待指定的 Child Thread 退出,并获得线程入口函数的返回值。此时 Parent Thread 会被阻塞。

函数原型

  • thread 参数:指定等待的 TID。
  • retval:是一个指向指针的指针类型,用于存储 Child 的退出码。
int pthread_join(pthread_t thread, void **retval);

Parent Thread 调用 pthread_join() 后,就会开始 Sleeping 等待 Child Thread 的执行结果。

pthread_exit()

函数作用:User Thread 调用后立即终止自己,并返回一个退出码。

函数原型

  • retval:是一个指针类型,用于存储退出码。如果不需要返回值,则设置为 NULL。
void pthread_exit(void *retval);

pthread_exit() 会自动释放当前 User Thread 所占用的所有资源。但需要注意的是,如果 Main Thread 调用了 pthread_exit(),那么就会终止整个 User Thread,因此需要额外小心。

pthread_detach()

函数作用:用于将线程设置为可分离状态。可分离状态的线程在退出时,系统可以自动回收线程资源,而无需通过调用 pthread_join 函数来进行回收。

函数原型

  • thread 参数:指定 TID。
int pthread_detach(pthread_t thread);

多线程安全与多线程同步

多线程安全(Multi-Thread Safe),就是在多线程环境中,多个线程在同一时刻对同一份共享数据(Shared Resource,e.g. 寄存器、内存空间、全局变量、静态变量 etc.)进行写操作(读操作不会涉及线程安全的问题)时,不会出现数据不一致。

为了确保在多线程安全,就要确保数据的一致性,即:线程安全检查。多线程之间通过需要进行同步通信,以此来保证共享数据的一致性。

pthread 库提供了保证线程安全的方式:

  1. 互斥锁(Mutex):是一种线程安全机制,为共享数据加上一把锁,拥有锁的线程,才可以访问共享数据。以此保护共享数据不被多个线程同时访问。
  2. 条件变量(Condition Variable):是一种线程同步机制,用于判断线程是否满足了特定的竞争条件(Race Condition)。只有满足条件的线程,才可以获得互斥锁,以此来避免死锁的情况。

需要注意的是,线程安全检查的实现会带来一定的系统开销。

互斥锁(Mutex)

pthread_mutex_init()

函数作用:用于初始化一个互斥锁实体。

函数原型

  • mutex 参数:pthread_mutex_t 类型指针,用于指定要初始化的互斥锁。
  • attr 参数:pthread_mutexattr_t 类型指针,用于指定互斥锁的属性,例如:递归锁、非递归锁等,通常为 NULL。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

pthread_mutex_lock()

函数作用:User Thread 用于获取互斥锁。如果互斥锁已被 Other User Thread 获得,则当前 User Thread 会阻塞。

函数原型

  • mutex 参数:pthread_mutex_t 类型指针,用于指定要获取的互斥锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);

pthread_mutex_unlock()

函数作用:User Thread 用于释放互斥锁,互斥锁重回可用状态。如果当前 User Thread 并没有锁,则该函数可能会产生未定义行为。

函数原型

  • mutex 参数:pthread_mutex_t 类型指针,用于指定要释放的互斥锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);

条件变量(Condition Variable)

互斥锁和条件变量都是多线程同步的工具,但是它们的作用不同:

  • 互斥:互斥锁可以保护共享资源的访问,防止多个线程同时修改共享资源,但是它无法告知其他线程何时可以安全地访问共享资源,有可能导致死锁的发生。

举例来说,存在全局变量 n(共享数据)被多线程访问。当 TA 获得锁后,在临界区中访问 n,且只有当 n > 0 时,才会释放锁。这意味着当 n == 0 时,TA 将永远不会释放锁,从而造成死锁。

那么解决死锁的方法,就是设定一个条件:只有当 n > 时,TA 才可以获得锁。而这个条件,就是多线程之间需要同步的信息。即:在多线程环境中,当一个线程需要等待某个条件成立时,才可以获得锁,那么应该使用条件变量来实现。

  • 同步:pthread 条件变量提供了一种线程同步机制,当特定的事件发生时,它可以唤醒一个或多个在等待事件的线程,从而实现线程间的同步和协调。条件变量通常与互斥锁一起使用,以避免竞态条件和死锁的发生。

pthread_cond_init()

函数作用:用于初始化一个条件变量实体。

函数原型

  • cond 参数:pthread_cond_t 类型指针,用于指定要初始化的条件变量。
  • attr 参数:pthread_condattr_t 类型指针,用于指定条件变量的属性,通常为 NULL。
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

pthread_cond_wait()

函数作用:线程用于等待某个条件变量满足。

  1. 当 T1 线程调用 pthread_cond_wait() 时,会自动地释放掉互斥锁,并阻塞线程,开始等待。
  2. 直到另一个 T2 线程调用了 pthread_cond_signal() 或 pthread_cond_broadcast(),以此来通知 T1 条件变量满足了。
  3. 然后 T1 pthread_cond_wait() 重新获取指定的互斥锁并返回。

函数原型

  • cond 参数:pthread_cond_t 类型指针,用于指定要等待的条件变量。
  • mutex 参数:pthread_mutex_t 类型指针,用于指定要关联的互斥锁。在等待期间,线程将释放该互斥锁。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_signal()

函数作用:用于向等待条件变量的线程发送信号,唤醒其中的一个线程。

函数原型

  • cond 参数:pthread_cond_t 类型指针,用于指定要发送信号的条件变量。
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_broadcast()

函数作用:用于向等待条件变量的所有线程发送信号,唤醒所有等待的线程。

函数原型

  • cond 参数:pthread_cond_t 类型指针,用于指定要发送信号的条件变量。
int pthread_cond_broadcast(pthread_cond_t *cond);

互斥锁和条件变量配合使用

当一个线程需要某个条件成立后才可以访问共享数据时。需要先锁定一个互斥锁,然后检查条件变量,如果条件不满足,则需要挂起并等待。

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int data = 0;  // 共享数据

void *producer(void *arg)

    for (int i = 0; i < 10; i++) 
        pthread_mutex_lock(&mutex); // 加锁
        data++; // 修改共享数据
        pthread_cond_signal(&cond); // 发送信号
        pthread_mutex_unlock(&mutex); // 解锁
        sleep(1);
    
    pthread_exit(NULL);


void *consumer(void *arg)

    while (1) 
        pthread_mutex_lock(&mutex); // 加锁
        while (data == 0)  // 如果没有数据就等待信号
            pthread_cond_wait(&cond, &mutex);
        
        printf("data = %d\\n", data); // 打印共享数据
        data--; // 修改共享数据
        pthread_mutex_unlock(&mutex); // 解锁
        sleep(1);
    
    pthread_exit(NULL);


int main()

    pthread_t tid1, tid2;

    pthread_create(&tid1, NULL, producer, NULL);
    pthread_create(&tid2, NULL, consumer, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;

线程非安全标准库函数

C 语言提供的大部分标准库函数都是线程安全的,但是也有几个常用函数是线程不安全的,也称为不可重入函数,原因是使用了某些全局或者静态变量。

我们知道,全局变量和静态变量分别对应内存中的全局变量区和静态存储区,这些区域都是可以跨线程访问的。在多线程环境中,这些数据如果在没有加锁的情况下并行读写,就会造成 Segmentfault / CoreDump 之类的问题。

  • 不可重入函数汇总:

以上是关于c语言多线程pthread的问题的主要内容,如果未能解决你的问题,请参考以下文章

linux系统下,c语言pthread多线程编程传参问题

C语言多线程教程(pthread)(线程创建pthread_t,指定线程run方法pthread_create,加mutex锁,解锁,伪共享 false sharing假共享)

c语言怎么创建线程和使用

C语言创建线程问题(急)

linux C语言 多线程竞争(加锁解锁 pthread_mutex_tpthread_mutex_lock()pthread_mutex_unlock() 可解决)

多线程——Pthread