`openat` has basically solved that since 2.6.16 (which came out in 2006). There are still some uncommon APIs have been slow to gain `at` variants but there's usually a workaround (for example, `getxattrat` and family were only added in 6.13 (this January), but can be implemented in terms of `openat` + `fgetxattr`)
This is good because the current directory conceptually process wide for the user as well. If your program isn't a shell or performs a similar function then you probably should not change the working directory at all.
If you need thread-specific local paths just use one of the *at() variants that let you explicitly specify a directory handle that your path is relative to.